JavaScript運行機制和事件循環

1. 說明

讀過本文章後,您能知道:javascript

  • JavaScript代碼在瀏覽器中的執行機制和事件循環
  • 面試中常常遇到的代碼輸出順序問題

首先經過一段代碼來驗證你是否瞭解代碼輸出順序,若是你不知道輸出順序,那麼本文能夠幫助你瞭解:html

console.log(1)
setTimeout(function () {
  new Promise(function (resolve) {
    console.log(2)
    resolve()
  })
  .then(() => { console.log(3) })
})
setTimeout(function () {
  console.log(4)
})
console.log(5)

2. JavaScript執行機制

JavaScript語言的執行是單線程(single thread)的。java

所謂的單線程,就是指一次只執行一個任務,若是有多個任務,就必須排隊,前面一個任務完成,才能執行後面任務。node

這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等待,會拖延整個程序的執行。常見的瀏覽器無響應(假死),每每就是由於某一段JavaScript代碼長時間運行(好比死循環),致使整個頁面卡在這個地方,其餘任務沒法執行。web

爲了解決這個問題,JavaScript語言將任務的執行模式分紅兩種:同步(Synchronous)和異步(Asynchronous)。面試

同步任務:在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務。ajax

異步任務:不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。chrome

JavaScript執行機制:segmentfault

一、全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。
二、主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
三、一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",若是有有執行任務,則進入執行棧,開始執行。
四、主線程不斷重複上面的三步,此過程也就是常說的Event Loop(事件循環)。

3. JavaScript執行機制中名詞介紹

3.1 執行棧

當咱們調用一個方法的時候,js會生成一個與這個方法相對應的執行環境,也叫執行上下文,這個執行環境存在着這個方法的私有做用域、參數、this對象等等。由於js是單線程的,同一時間只能執行一個方法,因此當一系列的方法被依次調用的時候,js會先解析這些方法,把其中的同步任務按照執行順序排隊到一個地方,這個地方叫作執行棧。api

3.2 主線程

JavaScript是單線程的,那麼這個單線程就成爲主線程。而事件循環在主線程執行完執行棧代碼後,才執行的。因此主線程代碼執行時間過長,會阻塞事件循環的執行。只有當執行棧爲空的時候(同步代碼執行完畢),纔會執行事件循環來觀察有哪些事件回調須要執行,當事件循環檢測到任務隊列有事件就讀取出回調放到執行棧由主線程執行。

3.3 任務隊列

任務隊列也有時稱叫消息隊列、回調隊列。

異步操做會將相關回調添加到任務隊列中。而不一樣的異步操做添加到任務隊列的時機也不一樣,如onclick, setTimeout,ajax處理的方式都不一樣,這些異步操做是由瀏覽器內核的webcore來執行的,webcore包含下圖中的3種 webAPI,分別是DOM Binding、network、timer模塊。

  • DOM Binding 模塊處理一些DOM綁定事件,如onclick事件觸發時,回調函數會當即被webcore添加到任務隊列中。
  • network 模塊處理Ajax請求,在網絡請求返回時,纔會將對應的回調函數添加到任務隊列中。
  • timer 模塊會對setTimeout等計時器進行延時處理,當時間到達的時候,纔會將回調函數添加到任務隊列中。

3.4 事件循環

圖片描述

如上圖所示,JavaScript總體執行過程:

  1. 主線程運行的時候會生成堆(heap)和棧(stack);
  2. js從上到下解析方法,將其中的同步任務按照執行順序排列到執行棧中;
  3. 當程序調用外部的API時,好比ajax、setTimeout等,會將此類異步任務掛起,繼續執行執行棧中的任務,等異步任務返回結果後,再按照執行順序排列到任務隊列中;
  4. 主線程先將執行棧中的同步任務清空,而後檢查任務隊列中是否有任務,若是有,就將第一個事件對應的回調推到執行棧中執行,若在執行過程當中遇到異步任務,則繼續將這個異步任務排列到任務隊列中。
  5. 主線程每次將執行棧清空後,就去任務隊列中檢查是否有任務,若是有,就每次取出一個推到執行棧中執行,這個過程是循環往復的... ...,這個過程被稱爲「Event Loop 事件循環」。

也能夠參考以下連接:https://html.spec.whatwg.org/...

4. 宏任務和微任務

出現Promise後,JavaScript對於任務的定義除了廣義的同步任務和異步任務,又對任務作了更精細的定義,macrotask(宏任務)和 microtask(微任務):

  • macrotask(按優先級順序排列): script(你的所有JS代碼,「同步代碼」), setTimeout, setInterval, setImmediate(node的), I/O,UI rendering
  • microtask(按優先級順序排列):process.nextTick(node的),Promise(這裏指瀏覽器原生實現的 Promise), Object.observe, MutationObserver

注意:宏任務、微任務中出現的nodejs中的方法是nodejs專有的,瀏覽器的JavaScript環境暫時沒有支持。

4.1 事件循環對宏任務和微任務的處理

有了宏任務和微任務後,JavaScript事件循環對此處理方法以下形式:

  • js引擎首先從macrotask queue中取出第一個任務,執行完畢後,將microtask queue中的全部任務取出,按順序所有執行;
  • 而後再從macrotask queue(宏任務隊列)中取下一個,執行完畢後,再次將microtask queue(微任務隊列)中的所有取出;
  • 循環往復,直到兩個queue中的任務都取完。

注意::此處把執行同步代碼算成第一個宏任務了。

圖片描述

5. 一個實際例子講解JavaScript執行流程

以下代碼:

console.log('1');
setTimeout(function() {
  console.log('2');
  new Promise(function(resolve) {
      console.log('3');
      resolve();
  }).then(function() {
      console.log('4')
  })
})
console.log('5');
setTimeout(function() {
  console.log('6');
  new Promise(function(resolve) {
      console.log('7');
      resolve();
  }).then(function() {
      console.log('8')
  })
})
console.log('9');

上面代碼執行過程:

  • 第一輪事件循環

    • 總體script代碼(同步代碼)做爲第一個宏任務進入主線程,遇到console.log,輸出1
    • 遇到setTimeout,其回調函數被放到宏任務隊列中,暫記爲setTmineout1
    • 遇到console.log,輸出5
    • 遇到setTimeout,其回調函數被放到宏任務隊列中,暫記爲setTmineout2
    • 遇到console.log,輸出9
    • 一個宏任務執行結束,去微任務隊列查找是否有待執行的任務,沒有,結束
    • 第一輪循環結束,輸出:1 5 9
  • 第二輪事件循環

    • 從宏任務隊列中取出一個任務,即setTmineout1,開始執行
    • 遇到console.log,輸出2
    • 遇到Promise,建立Promise,輸出了3,同時把Promise.then回調函數放到微任務隊列
    • 一個宏任務執行結束,去微任務隊列查找是否有待執行的任務, 發現有微任務,所有取出放到執行棧執行
    • 執行微任務,此時就一個微任務,console.log,輸出4
    • 微任務執行結束
    • 第二輪循環結束,輸出:2 4
  • 第三輪事件循環與第二輪同樣,輸出:6 7 8
  • 事件循環發現全部任務都已經處理完畢,此時程序執行結束
  • 所有的輸出:1 5 9 2 3 4 6 7 8,可複製代碼到chrome瀏覽器控制檯中運行校驗結果

注意:瀏覽器環境JavaScript的執行機制和node中JavaScript的執行機制是不一樣的。

參考資料

這一次,完全弄懂 JavaScript 執行機制

JavaScript任務隊列的順序機制(事件循環)

搞懂 JavsScript 異步 —  事件輪詢

JS與Node.js中的事件循環

相關文章
相關標籤/搜索