從Javascript單線程談Event Loop

来源:http://www.cnblogs.com/LIUYANZUO/archive/2017/08/13/7353547.html
-Advertisement-
Play Games

假如面試回答js的運行機制時,你可能說出這麼一段話:“Javascript的事件分同步任務和非同步任務,遇到同步任務就放在執行棧中執行,而碰到非同步任務就放到任務隊列之中,等到執行棧執行完畢之後再去執行任務隊列之中的事件。”但你能說出背後的原因嗎? 先理解相關概念 線程與進程 進程:是系統資源分配和調度 ...


假如面試回答js的運行機制時,你可能說出這麼一段話:“Javascript的事件分同步任務和非同步任務,遇到同步任務就放在執行棧中執行,而碰到非同步任務就放到任務隊列之中,等到執行棧執行完畢之後再去執行任務隊列之中的事件。”但你能說出背後的原因嗎?

 

先理解相關概念

線程與進程

進程:是系統資源分配和調度的單元。一個運行著的程式就對應了一個進程。一個進程包括了運行中的程式和程式所使用到的記憶體和系統資源。

線程:線程是進程下的執行者,一個進程至少會開啟一個線程(主線程),也可以開啟多個線程。

 

同步和非同步

同步和非同步關註的是消息結果通信機制

同步發出調用,在沒有得到結果前,該調用不返回。但是一旦調用返回,就得到返回值

非同步發出調用後,調用直接返回,沒有返回結果。但結果由回調函數給出,至於什麼時候給出,不知道。(這個回調函數肯定是在當前js執行完後才執行)

 

阻塞與非阻塞

阻塞和非阻塞關註的是程式在等待調用結果時的狀態.

阻塞調用調用結果返回之前,當前線程被掛起。調用線程只有在得到結果後才會返回。
非阻塞調用在不能立刻得到結果之前,該調用不會阻塞當前線程。

 

為什麼JavaScript是單線程?

JavaScript是單線程,程式按照順序排列,前面的必須處理好,後面的才會執行。JavaScript的設計初衷是作為瀏覽器腳本語言,主要是簡單用戶交互、操作DOM等,所以這門語言要圍繞單線程來設計,否則出現複雜的同步問題。

由於JavaScript是單線程的,對於耗時的或者時間不確定的操作,我們可以使用非同步編程,因為非同步可以實現非阻塞操作。當然也可以用HTML5標準的Web Worker本文不作討論,詳細參考MDN文檔:點擊這裡

既然js是單線程執行的,那誰去輪詢大的任務隊列?這不矛盾了嗎?

 

Js的單線程與非同步矛盾嗎?

不矛盾!!!首先記住這句話:Js執行是單線程,但瀏覽器是多線程

1)JS的單線程

一個瀏覽器進程中只有一個JS的執行線程,同一時刻內只會有一段代碼在執行

 

2)瀏覽器多線程

查閱資料,有些文章也說是模塊,本文就以瀏覽器多線程來說,它常駐線程:

渲染引擎線程:負責頁面的渲染

JS引擎線程:負責JS的解析和執行(本文說的主線程就指js引擎線程)

定時器觸發線程:處理定時事件,比如setTimeout, setInterval

事件觸發線程:處理DOM事件

非同步http請求線程:處理http請求

        ......

瀏覽器Js使用場景,瀏覽器本身是典型的 GUI 工作線程(GUI 工作線程在絕大多數系統中都實現為事件處理,避免阻塞交互)。故瀏覽器是事件驅動的(Event driven),瀏覽器中很多行為是非同步,會創建事件並放入任務隊列中。

由於Javascript 是單線程,它需要藉助非同步完成耗時或者時間不確定的操作,這些操作由瀏覽器的其它線程執行,這形成了非同步事件驅動。非同步事件驅動往往由瀏覽器的兩個或以上常駐線程共同完成的例如ajax非同步請求是由JS執行線程和非同步http請求線程事件觸發線程共同完成的

 

事件迴圈機制(Event Loop)

相關概念

函數調用形成一個棧幀。

 1 function foo(b) {
 2   let a = 10;
 3   return a + b + 11;
 4 }
 5 
 6 function bar(x) {
 7   let y = 3;
 8   return foo(x * y);
 9 }
10 
11 console.log(bar(7));

當調用 bar 時,創建了第一個幀 ,幀中包含了 bar 的參數和局部變數。

bar 調用 foo 時,第二個幀就被創建,並被壓到第一個幀之上,幀中包含了 foo 的參數和局部變數。

foo 返回時,最上層的幀就被彈出棧(剩下 bar 函數的調用幀 )。

bar 返回的時候,棧就空了。

對象被分配在一個堆中,一個用以表示一個記憶體中大的未被組織的區域。

每一個線程只有一個棧,每一個程式只有一個堆。

隊列

一個 JavaScript 運行時包含了一個待處理的消息隊列。每一個消息都與一個函數相關聯。

當棧為空時,從隊列中取出一個消息進行處理。這個處理過程包含了調用與這個消息相關聯的函數。

當棧再次為空的時候,也就意味著消息處理結束。

 

任務隊列消息隊列

任務隊列是一個先進先出的數據結構,當主線程執行棧一清空,任務隊列的回調函數就自動進入主線程。任務分成兩種

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

2非同步任務:該任務不進入主線程、而進入任務隊列。當執行棧清空後,才去執行任務隊列中的任務。

 

非同步執行的運行機制

由於JavaScript只能一次執行一段代碼(由於其單線程性質),這些代碼塊中的每一個都“阻止”其他非同步事件的進度。這意味著當非同步事件發生時(如滑鼠點擊,定時器觸發或XMLHttpRequest完成),它將排隊等待稍後執行(這種排隊實際發生的確定會因瀏覽器到瀏覽器而異)。

1所有同步任務都在主線程上執行,形成一個執行棧

2、當遇到非同步任務時(IO設備操作等,就在任務隊列中添加一個事件,這個事件對應著該非同步任務的回調函數。

3執行棧中的所有同步任務執行完畢,系統就會讀取任務隊列,進入執行棧,開始執行。

4主線程不斷重覆第三步。這就形成了事件迴圈

結論:Javascript的事件分同步任務和非同步任務,遇到同步任務就放在執行棧中執行,而碰到非同步任務就放到任務隊列之中,等到執行棧執行完畢之後再去執行任務隊列之中的事件。

 

事件和回調函數的概念必要說明

  • 工作線程:是本文對除了js引擎線程之外的其它線程的統稱
  • 回調函數:在一個函數中調用另外一個函數。這裡指非同步場景下為了非阻塞那些被主線程掛起來的代碼。
  •  主線程讀取任務隊列,就是讀取裡面有哪些事件,執行對應的回調函數。

工作線程完成一項任務,就任務隊列中添加一個事件。這裡的完成任務是指完成操作(clickmousetouchajax的數據完全請求回來......),並非指執行它的回調函數

a.onclick = function () {
  console.log("roro")
}

如上段代碼,僅是操作了click,但並沒有執行回調函數列印roro

 

事件迴圈

事件迴圈是:主線程重覆從任務隊列中取消息(事件),執行對應回調函數的過程

 

 

上圖中,主線程運行的時候,產生堆(heap)和棧(stack),棧中的代碼調用各種外部API,它們在任務隊列中加入各種事件(clickloaddone)。只要執行引擎棧棧中的代碼執行完畢,主線程就會去讀取任務隊列,依次執行那些事件所對應的回調函數。

 

定時器

首先參考這篇外國人的文章:how-javascript-timers-work,定時器的執行原理及細節。

setTimeout()怎麼執行?

setTimeout(function () {
  console.log('a');
}, 5000)

       Javascript執行引擎主線程運行的時候,產生堆和棧。程式中代碼依次進入棧中等待執行,當調用setTimeout()方法時,瀏覽器的定時器線程下處理延時方法,當setTimeout方法執行5秒後到達觸發條件方法被添加到用於回調的任務隊列

       當執行引擎的執行棧為空,執行引擎開始輪詢檢查任務隊列是否有任務需要被執行,檢查到已經符合執行條件的延時方法,將延時方法console.log('a')壓入執行棧引擎發現調用了log()方法,於是又將log()方法入棧。然後對執行棧依次出棧執行,輸出‘a’,清空執行棧,整個執行完畢。

 

setTimeout(fn,0)是立即執行嗎?

javascript權威指南中:當setTimeout的延遲時間設置為0的時候,回調函數不會馬上執行,而是進入 事件隊列。

 

btn.onclick = function () {
  setTimeout(function () {
    console.log('a')
  }, 0);
}

 

setTimeout(fn,0)的含義是:指定某個任務在主線程的空閑時間,儘可能早執行。它被添加進任務隊列,因此要等到同步任務和任務隊列前一個事件都處理完,才執行。

HTML5標準規定了setTimeout()的第二個參數的最小值不得低於4ms,如果低於這個值,就會自動增加。老版本的瀏覽器允許最短間隔設為10ms詳細參考MDN文檔:最小延遲和超時嵌套

所以setTimeout(fn,0)並不是立即執行。假若你想實現0ms 的timeout可以用 window.postMessage(),本文不作討論。

 

兩道經典的面試題:

一)以下代碼輸出什麼?

function foo() {
  console.log('a')
  setTimeout(function () {
    console.log('b');
  }, 500)
}

for (let i = 0; i < 10000; i++) {
  foo()
}

執行結果首先全部輸出a,中間等待500ms,然後全部輸出b。上圖是個人理解,不恰當的地方請指出!

 

二)ajax非同步請求是否真的非同步?

1、JS的執行線程(主線程)發起非同步請求瀏覽器會開一條新的HTTP請求線程來執行請求,繼續執行中剩下的任務,

2、在新線程(HTTP請求線程)中,在執行請求的同時,瀏覽器會正常處理其他任務的執行。

3在未來的某一時刻當數據完全請求回來以後事件觸發線程監視到之前發起的HTTP請求已完成,會將指定的回調函數放入任務隊列中。

4當瀏覽器執行棧空閑時,去掃描任務隊列中的回調函數,依次壓入執行棧中處理。

所以:ajax請求是非同步由瀏覽器新開一個線程請求,事件回調的時候放入Event loop任務隊列等候處理。詳細的例子,可以參考MDN文檔對ajax的描述:同步和非同步

 

誤解:事件迴圈類似棧或隊列

這裡順帶提一下:事件迴圈雖然涉及到類似隊列的結構,並不是採用棧的方式處理任務。事件迴圈作為一個進程被劃分為多個階段,每個階段處理一些特定任務,各階段輪詢調度。這些階段可以是定時器處理,dom事件處理,ajax非同步處理......

 

結語

JavaScript引擎只有一個線程,強制非同步事件排隊等待執行,Javascript語言的事件迴圈,是瀏覽器的處理和行為。另外,本文是我個人的學習筆記,通篇結合個人的理解,在某些地方表述不嚴謹,如有錯誤,希望指出。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • http://www.jeasyui.com/download/list.php 下載版本1.5.2的easyui中文API,可在CSDN網站http://download.csdn.net/download/tkk_lcm/9932078下載,不過需要有CSND賬號和1個積分才行。 ...
  • 在Reina(視網膜)屏幕的手機上,使用CSS設置的1px的邊框實際會比視覺稿粗很多。在之前的項目中,UI告訴我說我們移動項目中的邊框全部都變粗了,UI把他的設計稿跟我的屏幕截圖跟我看,居然真的不一樣。沒有辦法,只有在後面的版本中去修改了,但是要改的話,需要知道是為什麼。所以查了很多資料,終於搞懂了... ...
  • 超多經典 canvas 實例 普及:<canvas> 元素用於在網頁上繪製圖形。這是一個圖形容器,您可以控制其每一像素,必須使用腳本來繪製圖形。 註意:IE 8 以及更早的版本不支持 <canvas> 元素。 貼士:全部例子都分享在我的 GayHub - https://github.com/bxm ...
  • 一、Flex的簡介 Flex 是 Flexible Box 的縮寫,意為"彈性佈局",用來為盒狀模型提供最大的靈活性。用六個字概括彈性佈局就是簡單、方便、快速。 flex( flexible box:彈性佈局盒模型),是2009年w3c提出的一種可以簡潔、快速彈性佈局的屬性。主要思想是給予容器控制內 ...
  • 在前端項目開發中,px,em,以及rem都是頁面佈局常用的單位,雖然它們是長度單位,但是所含的意義不一樣。通過複習和查閱,總結了以下知識。 px像素(Pixel) 定義:相對長度單位。像素px是相對於顯示器屏幕解析度而言的。(引自CSS2.0手冊) 特點: 1:px代表的是像素,用它設置字體大小時, ...
  • 普通的路人,普通地瞧。簡單粗暴地分析了 Zepto 的 Ajax 模塊,分析時使用的是目前最新 1.2.0 版本。 ...
  • 本文將介紹如何親手來完成一個yeoman的generator,以實現快速構建最適合自己的項目。 本文將實現的generator起名為ngtimo,依照yeoman的命名規矩就叫做generator ngtimo,是筆者這周末一晚上加一上午參考著yeoman官方給出的幾個generator( "gen ...
  • 若是沒有對編輯器做任何配置直接添加圖片的話,顯示的html內容如下圖所示:它會顯示出原圖片尺寸 所以必須要對圖片的初始顯示尺寸做控制:ueditor文件中找到image.js文件 在image.js中找到如下圖所示: 在此處添加上所要想顯示的尺寸! http://ueditor.baidu.com/ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...