Js 執行機制深層剖析

小蟲巨蟹發表於2017-06-14

”js是一門單執行緒的語言,js最大的特性是非同步“,這些說法已經見慣不怪了,然而不瞭解js的解釋執行始末,這些概念也就只是聽聽,真正遇到問題的時候,也只能一臉懵逼而已~

一、Event Loop 機制/非同步原理

Js 執行機制深層剖析
eventLoop.png

由於 Js 是一門單執行緒的語言,為了實現非同步特性,必須有一種行之有效的機制,Event Loop 就是這種機制

說 Js 是一門單執行緒語言,指的是它只有一個使用者執行執行緒,同一時刻只能執行一個任務,在你看不見的地方,還需要有很多其它執行緒/程式來排程

同步的情況

當程式開始執行的時候,將預設執行全域性執行上下文(將在後文中說明)中的程式碼,如果遇到同步函式,那麼將把當前執行上下文壓入棧中,進入同步函式的執行上下文執行(倘若在函式中又遇到了其它同步函式,將持續此過程),當函式執行完成,執行執行緒將從訊息執行緒中pop一個執行上下文進行執行

    global() {

        /**
         *  定義
         */
        EC3() {

        }
        EC2() {
            EC3(); //呼叫EC3
        }
        EC1() {
            EC2();//呼叫EC2
        }

        //呼叫EC1
        EC1();
    }複製程式碼

非同步的情況

如果當前執行上下文遇到了非同步操作,那麼它向事件監聽執行緒或者計時執行緒發出通知之後,將繼續執行,此時有兩種情況:
a) 計時函式,如:

    setTimeout(func, 1000);複製程式碼

此時,非同步通知將發給計時執行緒(Timer),一秒後,計時執行緒將會把 func 函式的執行上下文壓入訊息執行緒堆疊,執行執行緒在處理完當前執行上下文的時候從訊息執行緒堆疊中 Pop 出 func 的執行上下文進行執行(如果在此期間沒有別的執行上下文入棧的話)

所以,setTime(func, n); 並不能保證,在 n 毫秒之後 func 能被執行,這還得看執行執行緒當前在幹什麼了

b) 事件監聽執行緒,如果發起的是一個非同步io操作,如發起一個網路請求:

    $.ajax(url, params, callback);複製程式碼

那麼非同步事件將發給事件監聽執行緒去監聽,一旦網路請求完成,同樣的,事件監聽執行緒將把 callback 的執行上下文入棧,等待執行執行緒的召喚

二、js的執行上下文

上文反覆提到執行上下文,下面就對執行上下文進行一個深入剖析

如何理解執行上下文

所謂執行上下文,就是 Js 執行的時候的一個執行環境/作用域(scope),有如下幾種情況:

  1. 全域性執行上下文/作用域:js程式碼的預設執行環境(只有一個)
  2. 函式執行上下文/作用域:每個函式對應的執行環境(無限多個)
  3. eval 程式碼執行上下文:使用 eval 執行的腳步的執行環境
/**
 * 全域性執行上下文/作用域
 */
        console.log('在全域性環境中執行')

        function hello() {  //hello函式執行上下文/作用域
            var say = 'hello';

            function world() { //world函式執行上下文
                ...
            }
        }複製程式碼

全域性作用域中的方法、變數,可以被其它任何函式作用域所訪問,函式作用域中的方法變數,在子函式作用域中可以訪問,外部無法直接訪問

通過函式返回的子函式去訪問函式作用域的私有變數,也就形成了閉包

執行堆疊

也就是上文提到的 Event Loop,該執行緒以的形式儲存執行上下文,函式執行上下文的入棧出棧的過程,使得js得以在單執行緒的情況下,實現非同步特性

Js 執行機制深層剖析

執行上下文的建立與執行

每一次函式被呼叫,js直譯器都會為之建立新的上下文,此時可以分為兩個階段:
a) 上下文的建立階段:函式被呼叫,但尚未開始執行(程式碼分析預處理階段),此時會為執行上下文建立作用域鏈,建立變數、函式和引數以及求this的值

    executionContextObj = {
        scopeChain: { /* 變數物件(variableObject)+ 所有父執行上下文的變數物件*/ }, 
        variableObject: { /*函式 arguments/引數,內部變數和函式宣告 */ }, 
        this: {} 
    }複製程式碼

特別的,變數提升就是在這個階段發生的

b) 執行階段:指派變數的值和函式的引用並解釋執行程式碼

下面再用虛擬碼的形式來描述一下這個過程:

    //函式被呼叫
    1. 建立執行上下文
        a) 建立作用域鏈
        b) 建立變數、函式和引數
        c) 求this2. 開始執行在執行上下文上 執行
        ...
        a) 遇到同步函式
        b) 當前執行上下文入棧
        c) 重複以上過程
        ...
    3. 執行完成,往上一層 執行上下文  返回資料
    4. 從執行上下文棧pop出一個新的執行上下文執行複製程式碼

二、總結

瞭解 Js 直譯器一些底層原理,是非常有必要的,當程式的執行結果跟你的預期結果不一樣的時候,甚至看起來很詭異的時候,如何去解釋,就很體現能力了

更多精彩,邀您關注~~

相關文章