1背景
從敲下第一行“Hello World!”起,我們都無時無刻的使用並接觸著事件迴圈。
去年寫react的時候總會遇到設定setState,後面呼叫值不會改變的問題。會想到是非同步的原因,試著延時定時問題得到解決。
this.setState({status: 1})
console.log(this.state.status); //0
setTimeout(()=>{
console.log(this.state.status); //1
},100)
複製程式碼
問題可以解決,所以延時定時解決了之後的很多問題。
後面看到javascript的事件迴圈,才真正理解了這之中的原理。
2單執行緒
從學習JavaScript開始,就一直看書和資料說JavaScript是一門單執行緒的語言。JavaScript到處存在著非同步(剛開始認為非同步是主執行緒下開的子執行緒)是依靠事件迴圈來實現的。
3同步非同步
單執行緒就意味著,所有任務需要排隊,前一個任務結束,才會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等著。
如果計算量大,讀寫IO請求、Ajax開銷大。如果通過同步來獲取資料會造成程式阻塞。所以像這種開銷大的任務,我們使用非同步處理。
所有任務可以分成兩種,一種是同步任務(synchronous),另一種是非同步任務(asynchronous)。同步任務指的是,在主執行緒上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;非同步任務指的是,不進入主執行緒、而進入"任務佇列"(task queue)的任務,只有"任務佇列"通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒執行。
4事件迴圈
根據事件迴圈圖來理解這一段程式碼//首先整段程式碼進入主執行緒執行
let a= 5;
//同步程式碼立即執行,輸出a=5
console.log(a);
//按鈕繫結點選事件,當使用者點選按鈕事件回撥函式進入任務佇列,待主執行緒執行完畢呼叫該函式
oBtn.onclick(function () {
a = 10;
console.log(a, 'click');
})
//當延時定時時間到的時候,回撥函式進入任務佇列,待主執行緒執行完畢呼叫任務佇列中的函式。
setTimeout(function () {
console.log(a,'timeout');
},5000)
複製程式碼
5node中的事件迴圈
Node.js也是單執行緒的Event Loop,但是它的執行機制不同於瀏覽器環境。
- V8引擎解析JavaScript指令碼。
- 解析後的程式碼,呼叫Node API。
- libuv庫負責Node API的執行。它將不同的任務分配給不同的執行緒,形成一個Event Loop(事件迴圈),以非同步的方式將任務的執行結果返回給V8引擎。
- V8引擎再將結果返回給使用者。
除了setTimeout和setInterval這兩個方法,Node.js還提供了另外兩個與"任務佇列"有關的方法:process.nextTick和setImmediate。它們可以幫助我們加深對"任務佇列"的理解。
process.nextTick方法可以在當前"執行棧"的尾部----下一次Event Loop(主執行緒讀取"任務佇列")之前----觸發回撥函式。也就是說,它指定的任務總是發生在所有非同步任務之前。setImmediate方法則是在當前"任務佇列"的尾部新增事件,也就是說,它指定的任務總是在下一次Event Loop時執行,這與setTimeout(fn, 0)很像。
process.nextTick(function A() {
console.log(1);
process.nextTick(function B(){console.log(2);});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0)
// 1
// 2
// TIMEOUT FIRED
複製程式碼
上面程式碼中,由於process.nextTick方法指定的回撥函式,總是在當前"執行棧"的尾部觸發,所以不僅函式A比setTimeout指定的回撥函式timeout先執行,而且函式B也比timeout先執行。這說明,如果有多個process.nextTick語句(不管它們是否巢狀),將全部在當前"執行棧"執行。
現在,再看setImmediate。
setImmediate(function A() {
console.log(1);
setImmediate(function B(){console.log(2);});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0);
複製程式碼
哪個回撥函式先執行呢?答案是不確定。執行結果可能是1--TIMEOUT FIRED--2,也可能是TIMEOUT FIRED--1--2。setImmediate與setTimeout不分伯仲。
但在遞迴中,setImmediate永遠優於setTimeout
setImmediate(function (){
setImmediate(function A() {
console.log(1);
setImmediate(function B(){console.log(2);});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0);
});
// 1
// TIMEOUT FIRED
// 2
複製程式碼
最後感謝阮一峰老師的文章和張仁陽老師的思路。