JavaScript事件循環

寫在前面

提及javascript(如下簡稱js)這門語言,相信你們已經很是熟悉了,無論是前端開發仍是後端開發幾乎無時無刻都要跟它打交道。雖然說開發者天天幾乎都要操做js,可是你真的肯定你掌握了js的運行機制嗎!下面咱們就來聊聊這話題。javascript

JavaScript運行機制圖解

clipboard.png

上圖咱們能夠分爲兩部分:瀏覽器中的JS引擎運行環境Runtime,那它們的區別是什麼?前端

  • JS引擎:編譯並執行代碼的地方。

    如上圖中能夠看出JS引擎分爲兩大核心部分:棧和堆java

    棧(Stack):js代碼的執行都要壓到此棧中執行。 ajax

    堆:存放對象、數組的地方,js垃圾回收就是檢查這裏。後端

  • Runtime:瀏覽器的運行環境,它提供了一些對外接口供JS調用,如網絡請求接口。

JavaScript引擎是單線程的

JS引擎是單線程的,也就是說在一個時間段內,事情只能一件一件的按前後順序去作,第一件事沒作完就不能第二件事。那麼在js引擎中負責解釋和執行js代碼的線程只有一個,咱們能夠稱之爲主線程數組

固然瀏覽器的運行環境Runtime還提供一些其餘的線程,如定時器線程、ajax線程、事件線程、網絡請求和UI渲染的線程,爲了和js主線程分開,咱們這裏都統稱它們爲工做線程瀏覽器

因爲瀏覽器是多線程的,因此工做線程和js主線程均可以執行任務,線程間互不干擾。網絡

JavaScript同步(異步)任務

在JavaScript任務能夠分爲兩種:多線程

  • 同步任務:在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務,若前一個任務耗費很長時間,則後面的任務會一直處於等待狀態,即阻塞狀態。
  • 異步任務:在棧執行代碼的過程當中,如遇到異步函數,如setTimeout、異步Ajax、事件處理程序,會將這些異步代碼交給瀏覽器的工做線程來處理,咱們把這些任務稱之爲異步任務。異步任務是不進入主線程,而是進入任務隊列(queue task)。異步

    • 什麼異步函數?

      異步函數一般是由發起函數回調函數構成的。如:

      A(callback)

      • 函數A就是發起函數
      • callback就是回調函數
​

它們都是在主線程調用的,其中發起函數用來發起異步過程,回調函數用來處理結果。

如:`setTimeout(callback,1000)`

setTimeout就是發起函數、callback就是回調函數。

如:異步的Ajax
var xhr = new new XMLHttpRequest();
    xhr.onreadystatechange = callback; //callback爲回調函數
    xhr.open('get',url,true);
    xhr.send(null); // send爲發起函數

能夠看出發起函數和回調函數也能夠是分離的。

既然同步任務是在主線程中執行的,那麼異步任務什麼時候執行?

答:是這樣的,一旦棧中同步任務執行完畢後,系統就會經過事件循環機制讀取任務隊列中的任務一個個移到棧中去執行。

事件循環

當主線程中的任務執行完畢後,會從任務隊列中獲取任務一個個的放在棧中執行去執行,這個過程是循環不斷的,因此整個的這種運行機制又稱爲事件循環。

在js中,代碼最終都是在棧中執行的,棧結構的特色是:先進後出,後進先出

咱們來看下面代碼的運行結果:

function bar(){
    console.log(1);
    foo();
}

function foo(){
    par();
    console.log(3);
}

function par(){
    setTimeout(function(){
        console.log(2);
    },0);
}

bar();

運行的最終結果是:132。 爲何結果不是123呢?

下咱們來分析下代碼運行時入棧和出棧的過程。

首先當調用函數bar()時,此函數就會先入棧,其內部的console.log(1)也會隨之入棧執行。

clipboard.png

執行完console.log(1)後,就要出棧,因而控制檯先打印出結果1,只剩下bar()在棧中。接着再執行函數bar內部的函數foo,因而函數foo也開心的入棧了。

clipboard.png

執行函數foo的內部代碼,調用函數par(),因而函數par()也要跟着入棧。

clipboard.png

因爲函數par()內部執行遇到了異步函數setTimeout,異步函數則會由瀏覽器的Runtime運行環境的工做線程來處理,等定時器設置的時間到達就會被放到任務隊列中,此時棧的同步任務繼續執行。

clipboard.png

接着在執行par函數中的console.log(3),控制檯打印結果爲3 ,此時棧的代碼執行完畢後,會按照棧的特色進行

先進後出,後進先出順序進行出棧。出棧順序:先函數par()-->後函數foo()-->最後函數bar

最後只剩下異步任務,由主線程去獲取任務隊列中的任務放在棧中去執行。也能夠認爲棧中的同步代碼執行老是在讀取異步任務以前執行。

clipboard.png

最後執行setTimeout中的回調函數:結果控制檯輸出爲2。

setTimeout(function(){
        console.log(2);
},0);

因此代碼的最終運行結果爲132。

小結

  • js引擎是單線程執行js代碼,同步任務在棧中按順序執行,若是某一個同步任務沒有執行完畢,則後面的代碼將會處於阻塞等待狀態
  • 棧中若執行遇到了異步任務(如定時器、異步Ajax、事件),會將此異步任務經過瀏覽器對應的工做線程來處理。
  • 工做線程中的全部異步任務均會按照設定的時間進行等待,時間一到會被加入任務隊列。若是是異步ajax,則等待其返回結果後在加入到任務隊列
  • 當棧中爲空時,會經過事件循環來一個個獲取任務隊列中的任務放到棧中進行逐個運行。即棧中的同步任務老是在讀取異步任務以前執行
  • 定時器設置的時間不必定按照設定的時間進行執行,這得取決於棧中同步任務耗費的時間。由於棧中執行的同步任務若是耗費很長時間,則會影響到異步任務回調函數的執行。
相關文章
相關標籤/搜索