Javascript並發模型和事件循環


Javascript並發模型和事件循環

JavaScript的"並發模型"是基於事件循環的,這個並發模型有別於Java的多線程, javascript的並發是單線程的。

Javascript 中有個重要一塊,Event Loop,能把單線程的 JavaScript 使出 多線程的感覺。

"Event Loop是一個程序結構,用於等待和發送消息和事件。(a programming construct that waits for and dispatches events or messages in a program.)"

 

簡單的說,就是在程序中(不一定是瀏覽器)中跑兩個線程,一個負責程序本身的運行,作為主線程; 另一個負責主線程與其他線程的的通信,被稱為“Event Loop 線程" 。  每當遇到異步的 setTimeOut ,setInterval 這些異步任務,交給 EventLoop 線程,然后自己往后運行,等到主線程運行完后,再去 Event Loop 線程拿結果。

這種模型人稱 "asynchronous" 或 "non-blocking" 模行。 

我簡單的畫了一個 javascript 的執行圖,我們通過圖,逐步分析.

Javascript core

函數調用時所用的執行環境棧

當js方法被調用時,會進入一個執行環境(execution context),如果有另外一個方法被調用了(或者自身遞歸調用),會新建一個新的執行環境,並且代碼的執行會進入到這個新的執行環境.函數調用返回的時候重新回到原來的執行環境. 由此,代碼執行的過程便形成了一個執行環境棧,江湖人稱 "stack";

執行環境

execution context 是一個由ECMA定義的抽象的概念,所有的javascript代碼都是在 execution context 執行環境中執行的.

全局執行環境是最外層的一個執行環境.在Web瀏覽器中,全局執行環境認為是window對象.因此所有全局變量和函數都是作為window對象的屬性和方法創建的.

全局的代碼(inline中執行的代碼,通常包括js文件,html頁面,加載的)在全局執行環境中執行.每個方法調用都有一個與之關聯的執行環境.

某個執行環境中的所有代碼執行完畢后,該執行環境被銷毀,保存在其中的所有變量和函數定義也隨之銷毀. 全局執行環境直到頁面關閉時才被銷毀.

每個函數都有自己的執行環境,當執行流進入一個函數時,函數的執行環境就會被推入環境棧中. 而在函數執行之后,棧

將其環境棧彈出,把控制權返回給之前的執行環境.js中的執行流正是由這個方便的機制的控制着.

當代碼在一個執行環境中執行時,會創建變量對象的一個作用域鏈(scope chain).

作用域鏈的用途,確保當前執行環境能有序(不明白就接着看)的訪問所能獲取的變量和函數.

作用域鏈的前端,始終都是當前執行的代碼所在的執行環境的變量對象.

即當前作用域沒有,外層作用域兜着.

執行環境被創建時會有次序的進行一些工作.

  1. 首先,在方法的執行環境中,Activation 活動對象被創建.活動對象另有一套實現機制.可以認為是對象,但又很特殊,沒有prototype,不能在代碼中直接引用到.
  2. 下一步,在方法調用創建執行環境的時候,會創建 argument 對象(類數組對象,含有傳進來的參數,length,callee),活動對象中會有一個"argument"同名屬性來引用這個argument對象.
  3. 再下一步,執行環境會被指定一個作用域.作用域由承載對象的列表(或鏈)組成.當代碼在一個執行環境中執行,會創建變量對象的一個作用域鏈(scope chain).作用域鏈的前端,始終都是當前執行的代碼所在執行環境的變量對象(和上面提到的 Activation 活動對象是一個對象). 如果這個環境是函數(javascript 環境只有函數和全局環境這兩種), 則將其活動對象作為變量對象使用,活動對象在最開始時只包含一個arguments對象.

作用域鏈中下一個變量對象來自當前代碼的外層環境.以此類推,直到到達最外層的全局執行環境,這樣便構成了一條從底到上的作用域鏈.全局執行環境的變量對象始終都是作用域鏈的最后一個對象.

標示符解析是沿着作用域鏈一級一級的搜索標示符的過程.搜索過程始終都是從作用域鏈的最前端開始,然后主逐級向后回溯.

當進入執行環境

當進入執行環境(代碼即將但仍未執行時),變量對象也就是活動對象已經包含了以下這些屬性:

  1. 函數的所有形參,由名稱和對應值組成變量對象的屬性.
  2. 執行環境所在函數內部的所有子函數聲明,由名稱和對應值(函數對象 function object)組成變量對象的屬性.如果變量對象內已存在同名屬性,那么會被替換所有的變量聲明.
  3. 由名稱和對應值(undefined)組成變量對象的屬性,如果變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性.

需要說明的是,每個執行環境都有this,this的值取決於調用者和所執行代碼的類型,this值是在進入執行環境時就已經確定的. 取值與執行環境相關聯,並且在執行環境運行期間是不能被修改的.

this 執行上下文中的一個屬性.

在全局代碼中,this始終是全局對象本身.

在通常的函數調用中,this是由函數的調用者提供的,即被調用函數的父執行環境提供的.this值取決於函數調用的方式.

堆是一個對象互聯的網絡。用數學術語說就是“圖”。圖由節點及其之間的邊構成。節點和邊都是可被標記的:節點(對象)用對象構造器的名稱標記,邊則由屬性名稱標記。

從一個對象到另一個的邊序列被叫做路徑(path)。通常我們只對那些不重復經過同一節點兩次的簡單路徑(simple path)感興趣。

我們把垃圾收集器根節點到某個指定對象的路徑叫做retaining path。如果不存在這樣的路徑,則該對象被稱作無法達到的(unreachable),應在垃圾收集過程中被處置。

隊列

在web瀏覽器中,當事件發生時如果該事件有相應的監聽器,則該消息會被及時的加進消息隊列,如果沒有監聽器,該事件會被丟失.

javascript運行時伴隨一個待處理的消息(也就是任務)隊列,每個消息都關聯了相關的處理函數,當函數的執行棧為空時,即當前沒有正在執行的函數,那么,隊列會從中挑出一個去處理.處理過程包括調用相關的函數(不用擔心處理函數的作用域問題,因為在javascript中,作用域是詞法化作用域,是方法在定義的時候已經確定的,與調用無關.作用域鏈創建早於方法調用,得益於此,我們方能使用閉包),處理完成后,如果棧為空,則再次嘗試從隊列中挑選可處理的事件或任務. 從中可以很容易窺探到 setInterval,setTimeout 是怎么進行異步執行的了.

這,就是事件循環,event loop.

總結

通過上面的分析,javascript是不存在並發的,單線程何談並發? 這只是說說了非阻塞.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM