進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是作業系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。
單進程和多進程顧名思義就是只有一個進程就是單線程,有超過1個進程就是多進程
什麼是線程?線程(thread)是作業系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。線程是不能單獨存在的,它是由進程來啟動和管理的。
單線程和多線程一個進程可以包含一個線程的時候就是單線程,包含幾個線程的時候就是多線程。
瀏覽器的進程分類1 個瀏覽器(Browser)主進程:主要負責界面顯示、用戶交互、子進程管理,同時提供存儲等功能。1 個GPU 進程:Chrome 剛開始發布的時候是沒有 GPU 進程的。而 GPU 的使用初衷是為了實現 3D CSS 的效果,只是隨後網頁、Chrome 的 UI 界面都選擇採用 GPU 來繪製,這使得 GPU 成為瀏覽器普遍的需求。最後,Chrome 在其多進程架構上也引入了 GPU 進程。1 個網絡(NetWork)進程:主要負責頁面的網絡資源加載,之前是作為一個模塊運行在瀏覽器進程裡面的,直至最近才獨立出來,成為一個單獨的進程。多個渲染進程:核心任務是將 HTML、CSS 和 JavaScript 轉換為用戶可以與之交互的網頁,排版引擎 Blink 和 JavaScript 引擎 V8 都是運行在該進程中,默認情況下,Chrome 會為每個 Tab 標籤創建一個渲染進程。出於安全考慮,渲染進程都是運行在沙箱模式下。多個插件進程:主要是負責插件的運行,因插件易崩潰,所以需要通過插件進程來隔離,以保證插件進程崩潰不會對瀏覽器和頁面造成影響。事件循環(Event Loop)每個渲染進程都有一個主線程,並且主線程非常繁忙,既要處理 DOM,又要計算樣式,還要處理布局,同時還需要處理 JavaScript 任務以及各種輸入事件。要讓這麼多不同類型的任務在主線程中有條不紊地執行,這就需要消息隊列和事件循環系統來統籌調度這些任務。
單線程處理安排好的任務我們知道JS是單線程的,一般的代碼都是按順序執行的,也就是已知的安排好的任務都是按順序執行的,等這個任務執行完成之後就會推出線程。
單線程處理安排好的任務一般情況肯定沒有我們想的那麼好,全都是已知的任務,有可能中間插入其他任務需要執行,這個時候應該怎麼辦呢?
在線程運行中處理新任務我們可以加一個循環,等待事件進入,然後再執行
在線程運行中處理新任務處理其他線程發送過來的任務剛剛一直都在討論主線程上的任務執行,那如果有其他線程的任務發給主線程,這個時候主線程怎麼處理呢?
處理其他線程發送過來的任務消息隊列消息隊列是一種數據結構,可以存放要執行的任務。數據結構裡的隊列就是「先進先出」的特性,一般添加任務就是添加到隊列尾部,然後從隊列的頭部取出任務來操作。
在這裡插入圖片描述消息隊列+循環添加了一個消息隊列後,其他的線程發送過來的事件就添加到消息隊列的尾部,然後主線程會循環的從消息隊列的頭部取出任務再執行任務。
消息隊列+循環處理其他進程發送過來的任務剛剛我們是處理其他線程發送給主線程的任務,現在是處理其他進程發送過來的任務,一字之差,其實步驟差不多的。整個渲染進程會有一個IO線程來接收其他進程發送過來的任務,然後再把接收到的其他進程任務添加到消息隊列的尾部,渲染主線程就循環的取出任務,執行任務。
處理其他進程發送過來的任務消息隊列中的任務類型macro-task(宏任務)包括整體代碼script,setTimeout,setIntervalmicro-task(微任務)Promise的then,await 的下一行開始,process.nextTick(類似node.js版的"setTimeout")requestAnimationFrame(RAF)window.requestAnimationFrame() 告訴瀏覽器——你希望執行一個動畫,並且要求瀏覽器在下次重繪之前調用指定的回調函數更新動畫。該方法需要傳入一個回調函數作為參數,該回調函數會在瀏覽器下一次重繪之前執行。它是由系統來決定回調函數的執行時機的,會請求瀏覽器在下一次重新渲染之前執行回調函數。無論設備的刷新率是多少,requestAnimationFrame 的時間間隔都會緊跟屏幕刷新一次所需要的時間;例如某一設備的刷新率是 75 Hz,那這時的時間間隔就是 13.3 ms(1 秒 / 75 次)。需要注意的是這個方法雖然能夠保證回調函數在每一幀內只渲染一次,但是如果這一幀有太多任務執行,還是會造成卡頓的;因此它只能保證重新渲染的時間間隔最短是屏幕的刷新時間。具體還是可以看MDN:requestAnimationFrame
requestIdleCallbackwindow.requestIdleCallback()方法將在瀏覽器的空閒時段內調用的函數排隊。這使開發者能夠在主事件循環上執行後臺和低優先級工作,而不會影響延遲關鍵事件,如動畫和輸入響應。函數一般會按先進先調用的順序執行,然而,如果回調函數指定了執行超時時間timeout,則有可能為了在超時前執行函數而打亂執行順序。你可以在空閒回調函數中調用requestIdleCallback(),以便在下一次通過事件循環之前調度另一個回調。和requestAnimationFrame 每一幀必定會執行不同,requestIdleCallback 是撿瀏覽器空閒來執行任務。具體還是可以看MDN:requestIdleCallback
事件循環,宏任務,微任務的關係遇到promise.then等微任務的時候會放進微任務隊列尾部遇到setTimeout,setInterval 等宏任務的時候會放入宏任務的隊列尾部最大的宏任務執行完後,查看有沒有微任務,有的話執行微任務,沒有的話執行下一個宏任務執行setTimeout,setInterval 等宏任務的時候,如果裡面有promise.then等代碼的時候又要放入微任務隊列尾部,執行完setTimeout,setInterval 後查看有沒有要執行的微任務,沒有的話執行下一個宏任務事件循環,宏任務,微任務的關係requestAnimationFrame、requestIdleCallback、事件循環、宏任務,微任務的關係raf 在 render 中,requestIdleCallback 在 render 之後。raf在渲染之前執行的,requestIdleCallback 是在瀏覽器空閒時間才會執行,所以requestIdleCallback是最後執行的。
RAF、RIC、事件循環、宏任務,微任務的關係可以用這個代碼體驗一下執行順序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#animation{
background-color: aqua;
width: 100px;
height: 100px;
border:1px solid rebeccapurple;
}
</style>
</head>
<body>
<div id='animation'>animation</div>
<script>
window.requestIdleCallback(myNonEssentialWork);
const tasks = [
() => {
console.log("第一個任務");
},
() => {
console.log("第二個任務");
},
() => {
console.log("第三個任務");
},
];
function myNonEssentialWork (deadline) {
// 如果幀內有富餘的時間,或者超時
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && tasks.length > 0) {
work();
}
if (tasks.length > 0) window.requestIdleCallback(myNonEssentialWork);
}
function work () {
tasks.shift()();
// console.log('執行任務');
}
var start = null;
var element = document.getElementById('animation');
element.style.position = 'absolute';
function step(timestamp) {
console.log('222')
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.left = Math.min(progress / 10, 200) + 'px';
// if (progress < 2) {
// window.requestAnimationFrame(step);
// }
}
window.requestAnimationFrame(step);
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
resolve();
}).then(function() {
console.log('then');
})
console.log('console');
</script>
</body>
</html>Chrome瀏覽器的執行順序是
可以看到出現兩種情況,requestAnimationFrame列印出來的222會在setTimeout之前或者之後,是因為raf是由系統來決定回調函數的執行時機的,會請求瀏覽器在下一次重新渲染之前執行回調函數,這個下一次重新渲染的時機我們不能固定,所以列印出來的順序是不固定的。
舉個🌰setTimeout(function() {
console.log('setTimeout1');
})
new Promise(function(resolve) {
console.log('promise');
resolve();
}).then(function() {
console.log('then');
})
setTimeout(function() {
console.log('setTimeout2');
})
async function aaa() {
console.log('async1')
return 'async2'
}
async function test () {
let a = await aaa();
console.log('await',a)
}
test()
console.log('console');這個代碼列印出來的順序你知道嗎?
這一整塊代碼就是一個宏任務,先一行一行代碼看,看到一個setTimeout,先把它放到宏任務隊列的尾部標記為setTimeout1然後遇到new Promise 這個時候就是立刻執行了,所以先輸出「promise」再往下,又遇到了setTimeout,再把它放到宏任務隊列的尾部標記為setTimeout2,這個時候宏隊列是這樣的:【setTimeout2】【setTimeout1】遇到了test的函數調用,就直接執行test內部,遇到await也是直接執行aaa(),所以輸出「async1」,await這行後面的代碼都是放入微任務隊列尾部,這個時候微任務隊列長這樣:【await async2】【then】遇到最後一行代碼了,這個時候直接輸出「console」,至此最大的宏任務執行完畢。這個時候查看有沒有微任務,我們看到微任務隊列【await async2】【then】,然後從隊頭取出微任務,所以依次輸出then,await async2微任務隊列為空後,說明執行完畢,然後查看有沒有下一個宏任務我們從宏任務隊列的頭部取出「setTimeout1」,輸出「setTimeout1」,這個宏任務裡面並沒有微任務,所以這個setTimeout1宏任務結束開始新的setTimeout2宏任務,**輸出「setTimeout2」**後,這個宏任務裡面並沒有微任務,所以這個setTimeout2宏任務結束。至此沒有宏任務也沒有微任務了,所以就結束了。依次輸出的結果是結果小練習看看你有沒有掌握,下面幾個🌰輸出的結果是什麼呢?
PS:process.nextTick要在node環境才能運行出來
console.log('1');
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
(async () => {
console.log('1');
await new Promise((resolve, reject) => {
console.log('2');
setTimeout(() => {
console.log('3')
}, 0)
console.log('4')
})
console.log('5')
})();
1、李兵老師的瀏覽器工作原理與實踐(這是一個付費專欄,以下連結不屬於這個專欄) 2、https://juejin.im/post/6844903512845860872 3、https://www.jianshu.com/p/2771cb695c81 4、https://html.spec.whatwg.org/multipage/webappapis.html#event-loops 5、https://javascript.info/event-loop