衆所周知,js是一門單線程的編程語言,在設計之初,它就註定了單線程的命運,好比當咱們處理dom時,若是有多個線程同時操做一個dom,那將很是混亂。html
既然是單線程,那麼它必定有一套嚴謹的規則,來使代碼可以乖乖的按開發者的設計運行,今天咱們就來研究其中的奧祕,瞭解一下js的event loop(事件循環)。node
聊js事件環,繞不開聊異步(在個人另外一篇文章擁抱並扒光Promise中對Promise這種異步解決方案有詳細介紹)ajax
爲何要異步?假設沒有異步,咱們發送一個ajax請求,後端代碼運行的很慢,這時瀏覽器會發生阻塞,若是十秒才響應,這十秒咱們該幹嗎?(或許能夠看博爾特跑個百米)編程
雖然在網頁誕生之初,確實有這樣的狀況,但現在這樣的頁面是會被用戶罵孃的。因而異步的做用顯露無遺,js開啓一個異步線程,何時請求完成,何時執行回調函數,而這期間,其餘代碼也能夠正常運行。後端
既然是單線程,就像一次只能過一我的的獨木橋,人要排隊,那麼代碼也要排隊。這時,同步代碼和異步代碼的排隊機制是不同的api
同步:在主線程(至關於獨木橋上)上排隊的任務,前一個任務執行完,下一個任務才能夠執行,若是前一個任務沒執行完,下一個任務要一直等待。就像過獨木橋,前面的人不過去,你死等也得等,否則就5253B翻騰兩週半入水。瀏覽器
異步:主線程先無論IO設備,掛起處於等待中的任務,先運行排在後面的任務。等到IO設備返回告終果,再回過頭,把掛起的任務繼續執行下去。就像過獨木橋,你懼怕不敢過,你就讓後面的人先過,何時你敢了你再過。而你調整心態的過程,主線程不考慮。dom
主線程會不斷的重複以上三步,這樣就構成了事件環,用圖表示異步
經過這張圖,咱們能夠知道,主線程運行時,產生堆和執行棧,棧中的代碼會調用一些api,好比seTtimeou、click等,這些異步操做會講他們的回調放入callback queue中,當執行棧中的代碼運行完,主線程回去讀取queue中的任務。編程語言
console.log(1)
setTimeout(function(){
console.log(2)
})
console.log(3)
複製代碼
咱們都知道結果是1 3 2,結合上面咱們梳理一下這段代碼的執行順序
一、從上到下運行執行棧中的同步代碼console.log(1)
二、看到setTimeout,把回調函數放入任務隊列中去
三、執行console.log(3)
四、主線程上沒有任務了,去任務隊列中執行setTimeout的回調,console.log(2)
顯然node要比瀏覽器複雜一些,它的流程是這樣的:
Node還有一些不一樣,它提供了另外兩個與"任務隊列"有關的方法:process.nextTick和setImmediate。它們能夠幫助咱們加深對"任務隊列"的理解。
process.nextTick方法能夠在當前"執行棧"的尾部,下一次Event Loop(主線程讀取"任務隊列")以前,觸發回調函數。也就是說,它指定的任務老是發生在全部異步任務以前。
setImmediate方法則是在當前"任務隊列"的尾部添加事件,也就是說,它指定的任務老是在下一次Event Loop時執行,這與setTimeout(fn, 0)很像。
大概能夠理解成process.nextTick有權插隊
setTimeout(function(){
console.log(1)
})
process.nextTick(function () {
console.log(2);
process.nextTick(function (){
console.log(3)
});
});
setTimeout(function () {
console.log(4);
})
複製代碼
雖然1在上面,但結果是2 3 1 4,就像咱們上面說的,process.nextTick會在主線程讀取任務隊列時插隊
再看setImmediate
setImmediate(function () {
console.log(1);
setImmediate(function B(){
console.log(2)
})
})
setTimeout(function () {
console.log(3);
}, 0)
複製代碼
結果多是312,也多是132
爲何會出現上面有的先有的後的狀況呢,難道除了人類社會代碼世界也有特權麼,是的,咱們將任務分爲兩種:
微任務Microtask,有特權,能夠插隊,包括原生Promise,Object.observe(已廢棄), MutationObserver, MessageChannel;
宏任務Macrotask,沒有特權,包括setTimeout, setInterval, setImmediate, I/O;
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