[譯]執行上下文、做用域鏈和JS內部機制

執行上下文、做用域鏈和JS內部機制(Execution context, Scope chain and JavaScript internalsjavascript

1、執行上下文

執行上下文(Execution context EC)是js代碼的執行環境,它包括 this的值、變量、對象和函數。

js執行上下文有3種類型

1. 全局執行上下文(Global execution context GEC)

全局上下文是文件第一次加載到瀏覽器,js代碼開始執行的默認執行上下文。在瀏覽器環境中,嚴格模式下this的值爲undefined,不然this的值爲window對象。GEC只能有一個(由於js執行的全局環境只能有一個)。java

2. 函數執行上下文(Functional execution context FEC)

函數執行時建立函數執行上下文,每一個函數都有本身的執行上下文。FEC能夠獲取到GEC中的內容。當在全局上下文中執行代碼時js引擎發現一個函數調用,則建立一個函數執行上下文。瀏覽器

3. Eval

執行eval時建立ide

2、執行上下文棧

執行上下文棧Execution context stack (ECS)是執行js代碼時建立的執行棧結構。 GEC默認在棧的最裏層,當js引擎發現一個函數調用,則建立這個函數的 FEC並push進棧,js引擎執行棧頂上下文關聯的函數,一旦函數執行完,則將其 FEC pop出棧,並往下執行。

看個例子(動圖插不了棧動圖連接函數

var a = 10;

function functionA() {

    console.log("Start function A");

    function functionB(){
        console.log("In function B");
    }

    functionB();

}

functionA();

console.log("GlobalContext");
  1. 當上面的代碼在瀏覽器中加載時,js引擎先將GEC push入ECS中,當在GEC中調用functionA時,functionA執行上下文被push入棧,並開始執行functionA。
  2. 當functionB在functionA中被調用時,functionB的執行上下文被push入棧,開始執行functionB,當functionB中內容執行完,functionB執行上下文被pop出棧,此時棧頂爲functionA的執行上下文,繼續執行functionA的代碼,執行完後pop出棧,棧頂爲GEC
  3. 最終執行GEC中代碼,執行完pop整個代碼結束。

上面討論了js引擎如何處理執行上下文(push和pop),下面討論js引擎如何建立執行上下文,這個過程分爲兩個階段:建立階段和執行階段this

3、建立執行上下文

1. 建立階段(後面又叫編譯階段)

js引擎調用函數,但函數還沒開始執行階段。

js引擎在這個階段對整個函數進行一個編譯(compile the code),主要乾了下面三件事:code

(1) 建立Activation object 或 the variable object(後面就簡稱它可變對象吧,不知道有沒有專業的中文名)

可變對象是包含全部變量、函數參數和內部函數聲明信息的特殊對象,它是一個特殊對象且沒有__proto__屬性。cdn

(2)建立做用域鏈

一旦可變對象建立完,js引擎就開始初始化做用域鏈。做用域鏈是一個當前函數所在的可變對象的列表,其中包括GEC的可變對象和當前函數的可變對象。對象

(3)決定this的值

初始化this的值ip

下面經過一個例子進行說明

function funA (a, b) {
  var c = 3;
  
  var d = 2;
  
  d = function() {
    return a - b;
  }
}


funA(3, 2);

當調用funA和執行funA前的這段時間,js引擎爲funA建立了一個executionContextObj以下

executionContextObj = {
 variableObject: {}, // All the variable, arguments and inner function details of the funA
 scopechain: [], // List of all the scopes inside which the current function is
 this // Value of this 
}

可變對象包含參數對象(包含函數參數的細節),聲明的變量和函數,以下所示

variableObject = {
  argumentObject : {
    0: a,
    1: b,
    length: 2
  },
  a: 3,
  b: 2
  c: undefined,
  d: undefined then pointer to the function defintion of d
}
  1. argumentObject如上所示
  2. 函數中的變量會被初始爲undefined,參數也會在可變對象中呈現
  3. 若是變量在參數對象中已存在,js引擎選擇忽略
  4. js引擎在當前函數中遇到函數定義,會用函數名建立一個屬性指向函數定義存儲的堆內容

2. 執行階段

在此階段,js引擎會重掃一遍函數,用具體的變量的值來更新 可變對象,並執行代碼內容。

執行階段執行完後,可變對象的值以下:

variableObject = {
  argumentObject : {
    0: a,
    1: b,
    length: 2
  },
  a: 3,
  b: 2,
  c: 3,
  d: undefined then pointer to the function defintion of d
}

4、完整的例子

代碼以下

a = 1;

var b = 2;

cFunc = function(e) {
  var c = 10;
  var d = 15;
  
  a = 3
  
  function dFunc() {
    var f = 5;
  }
  
  dFunc();
}

cFunc(10);

全局編譯階段

當瀏覽器加載上面的代碼後,js引擎進入編譯階段,只處理聲明,不處理值。下面走讀一遍代碼:

  1. a被賦值1,但它並非個變量或函數聲明,js引擎在編譯階段什麼都不作;
  2. b變量聲明初始化爲undefined;
  3. cFunc函數聲明初始化爲undefined。

此時的

globalExecutionContextObj = {
  variableObject: { // 原文中有時用activationObj
      argumentObj : {
          length:0
      },
      b: undefined,
      cFunc: Pointer to the function definition
  },
  scopeChain: [GLobal execution context variable object],
  this: value of this
}

全局執行階段

再接着上面,js引擎進入執行階段並再過一遍。此時將會更新變量名和執行

  1. js引擎發現可變對象中沒有a屬性,所以在GEC中添加a屬性,並初始化爲1;
  2. 可變對象有b,直接更新b的值爲2;
  3. 接着是函數聲明,不作任何事;
  4. 最後調用cFunc,js引擎再次進入編譯階段建立一個cFunc的執行上下文。

此時

globalExecutionContextObj = {
  variableObject: {
      argumentObj : {
          length:0
      },
      b: 2,
      cFunc: Pointer to the function definition,
      a: 1
  },
  scopeChain: [GLobal execution context variable object],
  this: value of this
}

cFunc的編譯階段

因爲cFunc有個參數e,js引擎會在cFunc執行上下文對象可變對象添加e屬性,並初始化爲2

  1. js引擎查看cFunc執行上下文的可變對象沒有c,所以添加c,並初始化爲undefined,d相似;
  2. a = 3非聲明,跳過;
  3. 函數聲明,建立dFunc屬性指向函數的堆空間;
  4. 對dFunc執行語句忽略

此時

cFuncExecutionContextObj = {
  activationbj: {
      argumentObj : {
          0: e,
          length:1
      },
      e: 10,
      c: undefined,
      d: undefined
      dFunc: Pointer to the function definition,
  },
  scopeChain: [cFunc variable object, Global exection context variable object],
  this: value of this
}

cFunc的執行階段

  1. c和d獲取到初始化值;
  2. a不是cFunc執行上下文對象中的屬性,js引擎會在做用率鏈的幫助下轉到GEC(全局執行上下文),查找a是否在GEC中。若是不存在,則會在當前做用域建立並初始化它;若是GEC中有,則更新其值,這裏會更新爲3。js引擎只有在發現一個變量在當前執行上下文對象屬性中找不到時會跳轉到GEC中;
  3. 建立dFunc屬性並指向函數的堆內存
cFuncExecutionContextObj = {
  activationbj: {
      argumentObj : {
          0: e,
          length:1
      },
      e: 10,
      c: 10,
      d: 15
      dFunc: Pointer to the function definition,
  },
  scopeChain: [cFunc variable object, Global exection context variable object],
  this: value of this
}

調用dFunc,js引擎再次進入編譯階段,建立dFunc執行上下文對象。
dFunc執行上下文對象能夠訪問到cFunc和全局做用域中的全部變量和函數;一樣cFunc能夠訪問到全局的,但不能訪問dFunc中的;全局上下文對象不能訪問cFunc和dFunc中的變量和對象。
有了上面的概念,對hoisting(變量提高)應該更容易理解了。

5、做用域鏈

做用域鏈是當前函數所在的可變對象列表

看下面一段代碼

a = 1;

var b = 2;

cFunc = function(e) {
  var c = 10;
  var d = 15;
  
  console.log(c);
  console.log(a); 
  
  function dFunc() {
    var f = 5;
    console.log(f)
    console.log(c);
    console.log(a); 
  }
  
  dFunc();
}

cFunc(10);

當cFunc被調用時,cFunc的做用域鏈以下

Scope chain of cFunc = [ cFunc variable object, 
                     Global Execution Context variable object]

當dFunc被調用時,dFunc在cFunc中,dFunc的做用域鏈包含dFunc、cFunc和全局可變對象

Scope chain of dFunc = [dFunc variable object, 
                    cFunc variable object,
                    Global execution context variable object]

當咱們嘗試訪問dFunc中的f,js引擎查看f是否可從dFunc的可變對象中獲取,找到console輸出;
訪問c變量,js引擎首先在dFunc的可變對象中獲取,不能獲取,則到cFunc的可變對象中去獲取,找到console輸出;
訪問a變量,同上,最後找到GEC的可變對象,獲取到並console輸出

一樣,cFunc中獲取c和a相似

在cFunc中訪問不到f變量,但dFunc中能夠經過做用域鏈獲取到c和d

相關文章
相關標籤/搜索