事件循環
- JS分爲同步任務和異步任務
- 同步任務都在主線程上執行,形成一個執行棧
- 主線程之外,事件觸發線程管理着一個任務隊列,只要異步任務有了運行結果,就在任務隊列之中放置一個事件。
- 一旦執行棧中的所有同步任務執行完畢(此時JS引擎空閒),系統就會讀取任務隊列,將可運行的異步任務添加到可執行棧中,開始執行。
事件循環機制進一步補充:
上圖大致描述就是:
1)主線程運行時會產生執行棧
2)棧中的代碼調用某些api時,它們會在事件隊列中添加各種事件(當滿足觸發條件後,如ajax請求完畢)
3)而棧中的代碼執行完畢,就會讀取事件隊列中的事件,去執行那些回調
4)如此循環
5)注意,總是要等待棧中的代碼執行完畢後纔會去讀取事件隊列中的事件
關於定時器思考:
問:當調用setTimeout後, 是如何等待特定時間後才添加到事件隊列中的?是JS引擎檢測的麼?
答:它是由定時器線程控制的, 計時完成後就會將特定的事件推入事件隊列中。
宏任務與微任務
除了廣義的同步任務和異步任務,進一步,JS中又分爲兩種任務類型:macrotask和microtask,在ECMAScript中,microtask稱爲jobs,macrotask可稱爲task。
它們的定義?區別?簡單點可以按如下理解:
macrotask(又稱之爲宏任務),可以理解是每次執行棧執行的代碼就是一個宏任務(包括每次從事件隊列中獲取一個事件回調並放到執行棧中執行)\
- 每一個task會從頭到尾將這個任務執行完畢,不會執行其它
- 瀏覽器爲了能夠使得JS內部task與DOM任務能夠有序的執行,會在一個task執行結束後,在下一個 task 執行開始前,對頁面進行重新渲染 (task->渲染->task->…)
microtask(又稱爲微任務),可以理解是在當前 task 執行結束後立即執行的任務
- 也就是說,在當前task任務後,下一個task之前,在渲染之前
- 所以它的響應速度相比setTimeout(setTimeout是task)會更快,因爲無需等渲染
- 也就是說,在某一個macrotask執行完後,就會將在它執行期間產生的所有microtask都執行完畢(在渲染前)
分別是什麼樣的場景會形成macrotask和microtask呢?
- macrotask:主代碼塊,setTimeout,setInterval等(可以看到,事件隊列中的每一個事件都是一個macrotask)
- microtask:Promise,process.nextTick等
再根據線程來理解下:
- macrotask中的事件都是放在一個事件隊列中的,而這個隊列由事件觸發線程維護
- microtask中的所有微任務都是添加到微任務隊列(Job Queues)中,等待當前macrotask執行完畢後執行,而這個隊列由JS引擎線程維護 (感覺上是)
所以,總結下運行機制:
- 執行一個宏任務(棧中沒有就從事件隊列中獲取)
- 執行過程中如果遇到微任務,就將它添加到微任務的任務隊列中
- 宏任務執行完畢後,立即執行當前微任務隊列中的所有微任務(依次執行)
- 當前宏任務執行完畢,開始檢查渲染,然後GUI線程接管渲染
- 渲染完畢後,JS線程繼續接管,開始下一個宏任務(從事件隊列中獲取)
看一段示例代碼:
setTimeout(function () {
console.log("setTimeout---5");
})
new Promise(function (resolve, reject) {
console.log("Promise---1");
resolve("success");
}).then(function (e) {
console.log(e+"---3");
console.log("then---4");
})
console.log("console---2")
運行結果爲:
Promise---1
console---2
success---3
then---4
setTimeout---5
再看一段示例代碼:
console.log("1");
setTimeout(function(){
console.log("2");
new Promise(function(resolve,reject){
console.log("3");
resolve("4");
}).then(function(e){
console.log(e);
console.log("5")
})
})
new Promise(function(resolve,reject){
console.log("6");
reject("7");
}).then(function(e){
console.log(e);
console.log("don't run here");
},function(e){
console.log(e);
console.log("8");
})
setTimeout(function(){
console.log("9");
new Promise(function(resolve,reject){
console.log("10");
resolve("11");
}).then(function(e){
console.log(e);
console.log("12")
})
})
運行結果爲:
1
6
7
8
2
3
4
5
9
10
11
12