JS的執行是單線程的,也就是同一段時間只能作一件事。單線程執行意味着全部的任務須要排隊,只有前一個任務執行完畢,才能執行後一個任務。爲了充分提升JS執行的效率,全部的任務分爲同步任務和異步任務。html
1)同步任務ajax
同步任務指的是在主線程上排隊執行的任務,只有前一個任務執行完,才能執行後一個任務。瀏覽器
2)異步任務bash
異步任務指的不進入主線程,而是進入「任務隊列」(task queue)裏的任務。只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。併發
那麼JS是如何處理這些同步任務和異步任務呢?看下面介紹JS執行的併發模型。dom
JavaScript的併發模型基於「事件循環」機制。異步
1)Stack 執行棧函數
函數調用造成了一個棧幀。當函數被調用時,會建立一個執行幀,幀中包含該函數的參數和局部變量,若是該函數內部還有函數調用,就會接着建立另外一個幀,包含內部函數的參數和局部變量。若是存在全局函數調用,則全局函數壓在幀的最底部。當函數執行完畢後,函數所在的執行幀會被彈出棧外,函數所在的變量和做用域會被釋放。執行棧事件先進後出。oop
2)Heap 堆ui
Heap指存放對象的內存區域,對象會被分配在一個堆中,即用以表示一大塊非結構化的內存區域。在函數內使用對象,實際上是指向內存中對象的一個指針。
3)Queue 隊列
一個 JavaScript 運行時包含了一個待處理的任務隊列,任務隊列是先進先出。每個任務都關聯着一個用以處理這個任務的函數。在事件循環期間的某個時刻,JS運行時會從任務隊列中取出最早進入隊列中的一個任務進行處理。這個任務會被移出隊列,任務關聯的函數會在執行棧中建立新的棧幀,被執行。執行棧清空後,事件循環會再處理隊列中的下一條任務。
當一個任務須要太長時間才能處理完畢時,能夠縮短任務處理,或者將一個任務裁剪成多個任務。在瀏覽器裏,當一個事件發生且有一個事件監聽器綁定在該事件上時,任務會被隨時添加進隊列。
setTimeout(fn, delay),delay參數是指任務被實際加入到隊列中的最小延遲時間,若是隊列中沒有其餘任務,在這個延遲時間以後,任務會立刻被處理。可是若是有其餘任務,setTimeout會等待其餘任務處理完後再執行fn。
總之,隊列中的這些任務會在主線程的執行棧被清空時被依次讀取(任務隊列先進先出,即先被壓入隊列中的事件會被先執行)。
下面的圖更詳細的介紹JS的事件循環模型。
主要操做邏輯以下:
1)執行棧執行主線程任務,當有操做dom,ajax交互,定時器等操做時,這些任務會被移入到callback queue 任務隊列中。
2)當主線程任務執行完畢爲空時,會讀取callback queue隊列中的函數,進入主線程執行。
3)上述過程會不斷重複,造成Event Loop。
在一個事件循環中,異步事件返回結果後會被放入到任務隊列中。根據異步事件的類型,會被放入到 對應的宏任務列表或者微任務列表中,當執行棧爲空的時候,主線程會首先查看微任務隊列中的事件,執行微任務隊列中的事件後,再執行宏任務隊列中的事件。
1)Macrotasks 宏任務
包括 setTimeout, setInterval, setImmediate, I/O, UI rendering
2)Microtasks 微任務
包括 process.nextTick, Promises, Object.observe(廢棄), MutationObserver
console.log('1');
setTimeout(function() {
console.log('2');
}, 0);
Promise.resolve().then(function() {
console.log('3');
}).then(function() {
console.log('4');
});
console.log('5');
//輸出值
1
5
3
4
undefined
2
複製代碼
microtask會優先macrotask執行。在每一次事件循環中,macrotask 只會提取一個執行,而 microtask 會一直提取,直到 microtasks 隊列清空。
參考資料