為什么JavaScript是單線程?
Javascript引擎是單線程機制,首先我們要了解Javascript語言為什么是單線程。
JavaScript的主要用途主要是用戶互動,和操作DOM。如果JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另一個線程刪除了這個節點,這時這兩個節點會有很大沖突,為了避免這個沖突,所以決定了它只能是單線程,否則會帶來很復雜的同步問題。此外HTML5提出Web Worker標準,允許JavaScript腳本創建多個線程(UI線程, 異步HTTP請求線程, 定時觸發器線程...),但是子線程完全受主線程控制,這個新標準并沒有改變JavaScript單線程的本質。
任務隊列
單線程一個一個完成任務,前一個任務完成了,才會執行下一個任務,就是排隊一樣,不能插隊,只能前面的人完成才能輪到后一個。那么問題來了,加入一個人在那辦理很多任務,一時半會辦不完,難道就一直卡在那里嗎,所有任務可以分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous)。
所有同步任務都在主線程上執行,形成一個執行棧
主線程之外,還存在一個任務隊列。只要異步任務有了運行結果,就在任務隊列之中放置一個事件。
一旦執行棧中的所有同步任務執行完畢,系統就會讀取任務隊列,看看里面有哪些事件。那些對應的異步任務,于是結束等待狀態,進入執行棧,開始執行。
主線程不斷重復上面的第三步。
Event Loop(事件循環)
主線程從任務隊列中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為Event Loop(事件循環)
上圖中,主線程運行的時候,產生堆(heap)和棧(stack),。其中,堆里存放著一些對象。而棧中則存放著一些基礎類型變量以及對象的指針,棧中的代碼調用各種外部API,它們在"任務隊列"中加入各種事件(click,load,done)。只要棧中的代碼執行完畢,主線程就會去讀取"任務隊列",依次執行那些事件所對應的回調函數。
function read() {
console.log(1);
setTimeout(function () {
console.log(2);
})
console.log(3);
}
read()
//輸出的是132*/
setTimeout()的就表示當前代碼執行完(執行棧清空)以后,立即執行(0毫秒間隔)指定的回調函數。
除了廣義的同步任務和異步任務,我們對任務有更精細的定義:
Macrotask? (宏任務):
etImmediate:把回調函數放在事件隊列的尾部
setTimeout:定時器
setInterval:定時器
Microtask?微任務):
process.nextTick:把回調函數放在當前執行棧的底部
Promise:
事件循環的順序,決定js代碼的執行順序。進入整體代碼(宏任務)后,開始第一次循環。接著執行所有的微任務。然后再次從宏任務開始,找到其中一個任務隊列執行完畢,再執行所有的微任務。以下測試可以得到結論?
console.log('main1');
process.nextTick(function() {
? ? console.log('process.nextTick1');
});
setTimeout(function() {
? ? console.log('setTimeout');
? ? process.nextTick(function() {
? ? ? ? console.log('process.nextTick2');
? ? });
}, 0);
new Promise(function(resolve, reject) {
? ? console.log('promise');
? ? resolve();
}).then(function() {
? ? console.log('promise then');
});
console.log('main2');
輸出結果是:? main1 、 promise 、 main2 、 process.nextTick1 、 promise then 、
setTimeout ?、 process.nextTick2
代碼分析
首先執行main1是毫無疑問的,接下來看到process.nextTick這個函數,把他排到執行棧的底部(微任務) (未執行),在往下是一個setTimeout函數,應該放到宏任務隊里排列(未執行),在看到的,promise函數,執行console.log('promise');then里面要判斷排到nextTick的后面(未執行),最后看到輸出的main2;微任務執行完,宏任務在實行
總結:1. process.nextTick會比setTimeout先執行
2.setTimeout和setImmediate誰先執行,答案是不確定。
Node.js的Event Loop
1. V8引擎解析JavaScript腳本。
2. 解析后的代碼,調用Node API。
3. libuv庫負責Node API的執行。它將不同的任務分配給不同的線程,形成一個Event Loop(事件循環),以異步的方式將任務的執行結果返回給V8引擎。
4. V8引擎再將結果返回給用戶。