JavaScript異步機制

眾所周知,JavaScript是單線程,同一時刻只會有一段代碼在運行。JavaScript又具有異步的特性,這二者是否沖突呢。JavaScript又是怎樣在單線程的情況下執行異步代碼的呢?
??但凡單線程且具備異步特性的語言,都是event-driven(事件驅動)的,而驅動這個事件(event-handler)的平臺即驅動平臺。瀏覽器就是一個驅動平臺,JavaScript雖然是單線程的,但是瀏覽器是多線程的,瀏覽器通過暴露給JavaScript一些API(WebAPI),來實現異步的功能。JavaScript里面常用到的setTimeout、Ajax、DOM Events就是WebAPI。
??瀏覽器有兩個核心部分,渲染引擎和JavaScript引擎。JavaScript主線程執行時候,產生對和棧。JavaScript異步的實現過程描述如下:

  1. 程序中的代碼一次進入棧,等待執行,執行完畢后出棧。
  2. 當執行到setTimeout等WebAPI時,將setTimeout方法壓棧,瀏覽器內核的其他線程開始執行WebAPI的callback,setTimeout出棧。
  3. JavaScript主線程繼續執行后續代碼(完成進棧出棧的過程),同時瀏覽器處理callback方法,當callback達到觸發條件時,方法被添加到任務隊列。
  4. 棧中代碼執行完畢后,主線程去查看任務隊列中,是否有callback等待執行,如果有,將callback方法壓入棧中,執行完畢后出棧。

以下代碼

    alert(1);
    setTimeout("alert(2)", 0);
    for(var i = 3; i < 30; i++) {
      alert(i);
    }

運行,會發現,彈出1后并不會馬上彈出2,而是先彈出3-30,然后在彈出2,按照以上介紹的異步實現過程可以,程序會先執行alert(1),然后執行setTimeout方法,setTimeout方法是WebAPI,所以瀏覽器的其他線程會處理callback,callback方法0秒后就被觸發了,放入任務隊列,但是此時,JavaScript后續代碼還沒有執行完畢,JavaScript主線程會繼續執行后續代碼至全部執行完畢,此時棧為空,主線程才會將堆中的callback方法壓入棧中執行,所以setTimeout 0的callback不一定會0秒后執行。

以演講中的示例進一步說明(轉載地址
http://www.alloyteam.com/2015/10/turning-to-javascript-series-from-settimeout-said-the-event-loop-model/#prettyPhoto

s1

s2

以圖中代碼為例,執行引擎開始執行上述代碼時,相當于先講一個main()方法加入執行棧。繼續往下開始console.log('Hi')時,log('Hi')方法入棧,console.log方法是一個webkit內核支持的普通方法,而不是前面圖中WebAPIs涉及的方法,所以這里log('Hi')方法立即出棧被引擎執行。
s3

s4

console.log('Hi')語句執行完成后,log()方法出棧執行,輸出了Hi。引擎繼續往下,將setTimeout(callback,5000)添加到執行棧。setTimeout()方法屬于事件循環模型中WebAPIs中的方法,引擎在將setTimeout()方法出棧執行時,將延時執行的函數交給了相應模塊,即圖右方的timer模塊來處理。
s5

執行引擎將setTimeout出棧執行時,將延時處理方法交由了webkit timer模塊處理,然后立即繼續往下處理后面代碼,于是將log('SJS')加入執行棧,接下來log('SJS')出棧執行,輸出SJS。而執行引擎在執行萬console.log('SJS')后,程序處理完畢,main()方法也出棧。
s6

s7

s8

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容