異步 JavaScript - 事件循環

簡評:若是你對 JavaScript 異步的原理感興趣,這裏有一篇不錯的介紹。javascript

JavaScript 同步代碼是若是工做的

在介紹 JavaScript 異步執行以前先來了解一下, JavaScript 同步代碼是如何執行的。java

這裏有兩個概念須要瞭解:瀏覽器

** 執行上下文(Excution Context)**異步

執行上下文是一個抽象的概念,用於表示 JavaScript 的運行環境,任何代碼都會有一個執行上下文。async

全局代碼運行在全局執行上下文,函數裏的代碼運行在函數執行上下文,每個函數都有本身的執行上下文。函數

調用堆棧(Call Stack)oop

調用棧是一個具備 LIFO(後進先出)結構的棧,用於儲存代碼執行階段全部的執行上下文。線程

由於 JavaScript 是單線程的,因此 JavaScript 只有一個單獨的調用棧。code

咱們如下面例子介紹同步代碼執行過程。blog

const second = () => {
  console.log('Hello there!');
}
const first = () => {
  console.log('Hi there!');
  second();
  console.log('The End');
}
first();

建立全局上下文(由 main() 表示),並將全局上下文推到棧頂。而後依次將遇到函數執行上下文推到棧頂(若是函數中執行其餘他函數,其餘函數依次推到棧頂以此類推)。當函數執行完畢對應的執行上下文會從調用棧彈出,程序結束時全局上下文從調用棧彈出。

JavaScript 異步代碼是如何執行的?

經過上個章節咱們已經對調用棧和 JavaScript 的同步執行有了基本的瞭解,如今來看看 JavaScript 異步執行是如何工做的。

什麼是阻塞?

因爲 JavaScript 是單線程的,若是某個函數耗費的時間比較長,會阻塞後面的任務執行,這就形成了阻塞。解決阻塞最簡單的方法是函數直接返回不等待,使用異步回調來處理返回結果。

在瞭解 JavaScript 異步執行以前還須要知道一些概念,事件循環和回調隊列(也稱爲任務隊列或消息隊列)。

注意:Event Loop 、Web APIs 和 Message Queue 並非 JavaScript 引擎的一部分,而是瀏覽器運行時環境和 Nodejs 運行時環境的一部分。

咱們如下面代碼爲例,解釋異步代碼是如何執行的。

const networkRequest =()=> { 
  setTimeout(()=> { 
    console.log('Async Code'); 
  },2000); 
};
console.log('Hello World');
networkRequest();
console.log('The End');

當上述程序加載到瀏覽器時 console.log(‘Hello World’) 代碼執行時會一次在調用棧推入和彈出。遇到 networkRequest() 將其推入到調用棧頂。而後繼續將 networkRequest 內的 setTimeout 方法推入棧頂,隨後 setTimeout networkRequest 依次出棧。最後對 console.log(‘The End’) 進行入棧出棧。

當 timer 到期後會將 callback 推入 message queue(消息隊列)中,此時 callback 不會立刻執行。會等待事件循環調度。

事件循環

事件循環的做用是查看調用棧並肯定調用棧是否空閒。若是調用棧空閒,even loop 會查看消息隊列是否有待處理的 callback 須要觸發。例子中的消息隊列只包含一個 callback,當調用棧爲空的時候,even loop 會將 callback 推入調用棧中觸發 networkRequest 的回調。

DOM 事件

消息隊列還會包含來自 DOM 的事件回調,好比鼠標和鍵盤事件回調。例如:

document.querySelector('.btn').addEventListener('click',function callback(event) {
  console.log('Button Clicked');
});

對於 DOM 事件,當具體的事件觸發會將 callback 推入消息隊列中,等待 even loop 來調度執行。

ES6 job queue/micro-task queue

ES6 新增了 job queue/micro-task queue 概念,在 Promise 中用到。job queue 比 message queue 擁有更高的優先級。意味着 job queue 和 message queue 都有任務時會優先執行 job queue 中的任務。例如:

console.log('Script start');

// callback 在 message queue 中
setTimeout(function callback() {
  console.log('setTimeout');
}, 0);

// 任務在 micro-task queue 中
new Promise((resolve, reject) => {
    resolve('Promise resolved');
  }).then(res => console.log(res))
    .catch(err => console.log(err));
console.log('Script End');

// 輸出:
Script start
Script End
Promise resolved
setTimeout

再來看下一個例子(兩個 setTimeout 和 兩個 Promise):

console.log('Script start');
setTimeout(() => {
  console.log('setTimeout 1');
}, 0);
setTimeout(() => {
  console.log('setTimeout 2');
}, 0);
new Promise((resolve, reject) => {
    resolve('Promise 1 resolved');
  }).then(res => console.log(res))
    .catch(err => console.log(err));
new Promise((resolve, reject) => {
    resolve('Promise 2 resolved');
  }).then(res => console.log(res))
    .catch(err => console.log(err));
console.log('Script End');


//輸出爲
Script start
Script End
Promise 1 resolved
Promise 2 resolved
setTimeout 1
setTimeout 2

因而可知 micro-task queue 中的全部任務都會優先於 message queue 中的任務執行。

原文:Understanding Asynchronous JavaScript — the Event Loop

相關文章
相關標籤/搜索