js是單線程的,處理任務是一件接着一件處理,因此若是一個任務須要處理好久的話,後面的任務就會被阻塞html
因此js經過Event Loop事件循環的方式解決了這個問題,在瞭解事件循環前,咱們須要瞭解一些關鍵詞web
js引擎運行時,當代碼開始運行的時候,會將代碼,壓入執行棧進行執行chrome
例: api
當代碼被解析後,函數會依次被壓入到棧中promise
有入棧,就要有出棧,當函數c執行完,開始出棧瀏覽器
前面執行棧,先入後出,但其實也是同步的,同步就意味着會阻塞,因此須要異步,那當執行棧中出現異步代碼會怎麼樣bash
此時在代碼中,添加點擊事件和setTimeout,如今觀察一下執行順序併發
觀察此時的執行棧效果,和上面的函數嵌套有顯著區別異步
1,console.log("sync")的語句,不會被壓入到執行棧底部,由於console已經執行結束了函數
2,click和settimeout都入棧了,但它們內部的console沒有入棧的,這說明他們沒有執行完
3,若是click沒有執行完,那爲何setTimeout會入棧,不該該被阻塞嗎?
答案是:當瀏覽器在執行棧執行的時候,發現有異步任務以後,會交給webapi去維護,而執行棧則繼續執行後面的任務
一樣,setTimeout一樣會被添加到webapi中
webapi是瀏覽器本身實現的功能,這裏專門維護事件。
上面setTimeout旁邊有個進度條,這個進度就是設置的等待時間
上面的例子,當setTimeout執行結束的時候,是否是就應該回到執行棧,進行執行輸出呢?
答案:並非!
此時,倒計時結束後的setTimeout的可執行函數,被放入了回調隊列
最後,setTimeout的可執行函數,被從回調隊列中取出,再次放入了執行棧
這樣的執行過程就叫 event loop事件循環
上面是一個最簡單的例子,輸出結果是1,3,2
這是爲何?
上圖展現了具體的執行順序:
1,console.log(1)被壓入執行棧
2,setTimeout在執行棧被識別爲異步任務,放入webapis中
3,console.log(3)被壓入執行棧,此時setTimeout的可執行代碼還在回調隊列裏等待
4,console.log(3)執行完成後,從回調隊列頭部取出console.log(2),放入執行棧
5,console.log(2)執行
須要格外注意,回調隊列是先進先出的,例:
當console.log(4)執行完成後,從回調隊列裏取出了console.log(2);
注意:只有console.log(2)執行完成,執行棧再次清空時,纔會從回調隊列取出console.log(3)
上面的代碼最後輸出1,5,2,4,3,執行過程:
1,輸出1,將2push進回調隊列
2,將4push進回調隊列
3,輸出5
4,清空了執行棧,讀取輸出2,發現有3,將3push進回調隊列
5,清空了執行棧,讀取輸出4
6,清空了執行棧,讀取輸出3
至此,看起來好像沒問題了,可是!!!!!!,事情尚未結束
經過上面的例子,想必已經對event loop有了必定的瞭解,如今繼續看一個例子
console.log(1);
setTimeout(()=>{
console.log(2)
})
var p = new Promise((resolve,reject)=>{
console.log(3)
resolve("成功")
})
p.then(()=>{
console.log(4)
})
console.log(5)
複製代碼
按照event loop的概念,應該是1,3,5,2,4,由於setTimeout和then會被放到回調隊列裏,而後又是先進先出,因此應該是2先輸出,4後輸出
但事實輸出的順序是1,3,5,4,2!
這是由於promise的then方法,被認爲是在Microtask微任務隊列當中
Macrotask(宏任務)很好理解,就是我們前面介紹過的回調隊列callback queue
Microtask(微任務)一樣是一個任務隊列,這個隊列的執行順序是在清空執行棧以後
用圖展現就是
能夠看到Macrotask(宏任務)也就是回調隊列上面還有一個Microtask(微任務)
Microtask(微任務)雖然是隊列,但並非一個一個放入執行棧,而是當執行棧請空,會執行所有Microtask(微任務)隊列中的任務,最後纔是取回調隊列的第一個Macrotask(宏任務)
例:
上面的執行過程是:
1,將setTimeout給push進宏任務
2,將then(2)push進微任務
3,將then(4)push進微任務
4,任務隊列爲空,取出微任務第一個then(2)壓入執行棧
5,輸出2,將then(3)push進微任務
6,任務隊列爲空,取出微任務第一個then(4)壓入執行棧
7,輸出4
8,任務隊列爲空,取出微任務第一個then(3)壓入執行棧
9,輸出3
10,任務隊列爲空,微任務也爲空,取出宏任務中的setTimeout(1)
11,輸出1
這和每一個瀏覽器有關,每一個瀏覽器實現的promise不一樣,有的then是宏任務,有的是微任務,chrome是微任務,廣泛都默認爲微任務
除了then之外,還有幾個事件也被記爲微任務:
console.log("start");
setImmediate(()=>{
console.log(1)
})
Promise.resolve().then(()=>{
console.log(4);
})
Promise.resolve().then(()=>{
console.log(5);
})
process.nextTick(function foo() {
console.log(2);
});
process.nextTick(function foo() {
console.log(3);
});
console.log("end")
複製代碼
上面代碼輸出start,end,2,3,4,5,1
process.nextTick的概念和then不太同樣,process.nextTick是加入到執行棧底部,因此和其餘的表現並不一致
console.log("1");
setTimeout(()=>{
console.log(2)
Promise.resolve().then(()=>{
console.log(3);
process.nextTick(function foo() {
console.log(4);
});
})
})
Promise.resolve().then(()=>{
console.log(5);
setTimeout(()=>{
console.log(6)
})
Promise.resolve().then(()=>{
console.log(7);
})
})
process.nextTick(function foo() {
console.log(8);
process.nextTick(function foo() {
console.log(9);
});
});
console.log("10")
複製代碼
執行順序:
1,輸出1
2,將setTimeout(2)push進宏任務
3,將then(5)push進微任務
4,在執行棧底部添加nextTick(8)
5,輸出10
6,執行nextTick(8)
7,輸出8
8,在執行棧底部添加nextTick(9)
9,輸出9
10,執行微任務then(5)
11,輸出5
12,將setTimeout(6)push進宏任務
13,將then(7)push進微任務
14,執行微任務then(7)
15,輸出7
16,取出setTimeout(2)
17,輸出2
18,將then(3)push進微任務
19,執行微任務then(3)
20,輸出3
21,在執行棧底部添加nextTick(4)
22,輸出4
23,取出setTimeout(6)
24,輸出6
最後結果是:1,10,8,9,5,7,2,3,4,6