js是單執行緒的
- 因為是單執行緒,所以所有任務都需要排隊,前一個任務結束,後一個任務才能執行,如果前一個任務花費時間較長,後一個任務等待時間也隨之變長。
- js可以做到先把等待中的任務先放一邊晾著,去處理後面的任務。
- 於是所有任務可以分為兩種,一種是同步任務,另一種是非同步任務:
- 同步任務很簡單,前面的任務完成後面才能執行,一個接一個的執行任務。
- 非同步任務不佔用主執行緒,直接進入“任務佇列”中,等任務佇列通知主執行緒,某個任務可以執行了,才會進入主執行緒執行。
非同步執行的執行機制
- 所有同步任務都在主執行緒上執行,形成一個執行棧
- 主執行緒之外,還存在一個“任務佇列”。只要非同步任務有了執行結果,就在“任務佇列”中放置一個事件
- 一旦執行棧中的所有同步任務都執行完畢,就會去“任務佇列”中讀取新的任務放到執行棧中,再依次執行任務
- 只要主執行緒空了,就會讀取任務佇列,這就是js的執行機制。這個過程會不斷的重複
再說說事件和回撥函式
- 任務佇列其實存放的是事件的佇列,主程式讀取任務佇列,其實就是在讀有哪些事件罷了
- 只要指定過回撥函式,這些事件發生時就會進入任務佇列中,等待主執行緒讀取
- 非同步任務必須指定回撥函式,當主執行緒執行非同步任務時,其實就是在執行對應的回撥函式
- 任務佇列是一個先進先出的資料結構,排在前面的先執行。當呼叫棧中的任務空了後,主執行緒會自動呼叫任務佇列裡的任務執行
來看看Event Loop
- 主執行緒從任務佇列中讀取任務,這個過程是不斷重複的,所以被稱為Event Loop(事件迴圈),從字面意思就清楚了
- 再來看一張圖
上圖中,主執行緒產生了heap(堆)和stack(棧),棧中的程式碼呼叫各種api,然後在任務佇列中加入click,load,done等事件,當棧中的任務都執行完後就去呼叫任務佇列中的事件並依次執行
function one() {
var a = 1;
two();
function two() {
var b = 2;
three();
function three() {
console.log(b);
}
}
}
one();
// 棧:是先進後出 函式呼叫就是最常見的形式
// one -> two -> three 依次執行
// 銷燬過程是three -> two -> one
複製程式碼
任務佇列中的定時器
console.log(1);
setTimeout(function () {
console.log('定時器');
},0); // 如果不寫時間,預設是4ms
let p = new Promise(function (resolve, reject) {
console.log(3);
resolve(100);
}).then(function (data) {
console.log(data);
});
console.log(2);
// 1 3 2 100 '定時器'
複製程式碼
Node
先來看看node是如何工作的
NodeJs的執行機制:
- V8引擎解析js程式碼
- 程式碼中可能會呼叫node API,node會交給LIBUV庫處理
- LIBUV通過阻塞I/O和多執行緒實現了非同步I\O
- 將任務的執行結果返回給V8引擎,V8引擎再將結果返回給使用者
Node中的Event Loop
在LIBUV內部有這樣一個事件環機制,在node啟動時會初始化事件環
- 這裡每一個階段都對應一個事件佇列,當Event Loop執行到某一階段的時候會將該階段對應的事件依次執行。
- 當佇列執行完畢or執行的數量超過上限的時候,會自動轉入下一階段。 這裡我們重點關注一下poll階段
poll階段
總結一下:以上內容就是關於事件環的整體流程,也可以理解一下同步和非同步的區別