Javascript執(zhí)行上下文

原文章資料:Understanding Execution Context and Execution Stak in Javascript

什么是 Excution Context

簡單的說,執(zhí)行上下文是 Javascript 代碼計算和被執(zhí)行環(huán)境的抽象概念。Javascript 代碼在哪里執(zhí)行,哪里就是執(zhí)行上下文。

Excution Context 的種類

  • 全局執(zhí)行上下文 —— 最基礎(chǔ)的執(zhí)行上下文,一個運(yùn)行的 Javascript 程序只存在一個全局執(zhí)行上下文。寫在函數(shù)體之外的代碼都處在全局執(zhí)行上下文。他做了兩件事情:創(chuàng)建了一個全局對象(瀏覽器中是window對象,node環(huán)境中是global),并把全局對象賦值給 this。
  • 函數(shù)執(zhí)行上下文 —— 每當(dāng)函數(shù)被調(diào)用,函數(shù)內(nèi)部就會生成一個新的執(zhí)行上下文。每個函數(shù)都有自己的執(zhí)行上下文,但是只有當(dāng)函數(shù)被調(diào)用的時候,才會被創(chuàng)建。
  • eval 函數(shù)執(zhí)行上下文 —— eval 函數(shù)中的代碼也有自己的執(zhí)行上下文,但是 eval 并不常用。

Excution Stack

執(zhí)行棧,又稱“調(diào)用?!?,是一個先進(jìn)后出的結(jié)構(gòu)(LIFO, Last in First Out),用來存放代碼執(zhí)行過程中所有的執(zhí)行上下文。

Javascript 引擎在執(zhí)行代碼時,創(chuàng)建并把全局執(zhí)行上下文壓入 Excution Stack;每當(dāng)執(zhí)行到函數(shù)調(diào)用的時候,就會創(chuàng)建新的函數(shù)執(zhí)行上下文,然后從棧頂端推入棧中。

Javascript 引擎從頂端開始執(zhí)行函數(shù),當(dāng)函數(shù)被執(zhí)行完成后,它的執(zhí)行上下文就會被推出棧,然后再去執(zhí)行棧中的下一個上下文。

?? 簡單的例子:

let a = 'Hello World!';
function first() {
  console.log('Inside first function');
  second();
  console.log('Again inside first function');
}
function second() {
  console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
圖片來自于原博客

執(zhí)行上下文是如何創(chuàng)建的

創(chuàng)建上下文分為兩步:

  1. 創(chuàng)建階段
  2. 執(zhí)行階段

創(chuàng)建階段:

LexicalEnvironment:詞法環(huán)境
VariableEnvironment:變量環(huán)境

  1. 創(chuàng)建詞法環(huán)境組件
  2. 創(chuàng)建變量環(huán)境組件

Lexical Environment

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.

詞法環(huán)境是用來定義 基于ECMAScript代碼詞法嵌套結(jié)構(gòu)的具體變量和函數(shù)的關(guān)聯(lián) 的具體類型。詞法環(huán)境由環(huán)境記錄器和一個只想外部詞法環(huán)境的 引用(outer) 組成。

每一個 詞法環(huán)境 由以下結(jié)構(gòu)組成:

  1. Environment Record
  2. Reference to the outer environment
  3. This binding
Environment Record

存儲詞法環(huán)境中 定義的變量和函數(shù)。

  1. 聲明式環(huán)境記錄器存儲變量、函數(shù)和參數(shù)。
  2. 對象環(huán)境記錄器用來定義出現(xiàn)在全局上下文中的變量和函數(shù),并且存儲了全局對象包含的方法和屬性。

對于 函數(shù)內(nèi)部的詞法環(huán)境,環(huán)境記錄器還會記錄一個 arguments 對象(存儲參數(shù)的索引和值的鍵值對)和參數(shù)的長度。這個結(jié)構(gòu)看上去如何下:

function foo(a, b) { var c = a + b }
// argument object
Arguments: {0: 2, 1: 3, length: 2}
Reference to the outer environment

外部環(huán)境的引用意味著它可以訪問其父級詞法環(huán)境(作用域)

this 的綁定

在全局執(zhí)行上下文中,this 的值指向全局對象。(在瀏覽器中,this引用 Window 對象)。

在函數(shù)執(zhí)行上下文中,this 的值取決于該函數(shù)是如何被調(diào)用的。如果它被一個引用對象調(diào)用,那么 this 會被設(shè)置成那個對象,否則 this 的值被設(shè)置為全局對象或者 undefined(在嚴(yán)格模式下)。例如:

const person = {
  name: 'peter',
  birthYear: 1994,
  calcAge: function() {
    console.log(2018 - this.birthYear);
  }
}
person.calcAge(); 
// 'this' refers to 'person', because 'calcAge' was called with //'person' object reference
const calculateAge = person.calcAge;
calculateAge();
// 'this' refers to the global window object, because no object reference was given

抽象來說,詞法環(huán)境的結(jié)構(gòu)類似下面代碼表示:

GlobalExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: "Object",
            // Identifier bindings go here
        }
        outer: <null>,
        this: <global object>
    }
}
FunctionExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: "Declarative",
            // Identifier bindings go here
        },
        outer: <Global or outer function environment reference>,
        this: <depends on how function is called>
    }
}

Variable Environment

它同樣是一個詞法環(huán)境,其環(huán)境記錄器持有變量聲明語句在執(zhí)行上下文中創(chuàng)建的綁定關(guān)系。它有著上面定義的詞法環(huán)境的所有屬性。

在 ES6 中,詞法環(huán)境組件和變量環(huán)境的一個不同就是前者被用來存儲函數(shù)聲明和變量(letconst)綁定,而后者只用來存儲 var 變量綁定。

執(zhí)行階段

在此階段,將完成對所有這些變量的分配,并最終執(zhí)行代碼。

例子??

let a = 20;
const b = 30;
var c;

function multiply(e, f) {
    var g = 20;
    return e * f * g;
}
c = multiply(20, 30);

當(dāng)上面的代碼執(zhí)行時,Javascript引擎會創(chuàng)建一個全局執(zhí)行上下文來執(zhí)行全局代碼。全局上下文創(chuàng)建階段看上去和這個差不多:

GlobalExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: < func >
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      c: undefined,
    }
    outer: <null>,
    ThisBinding: <Global Object>
  }
}

在變量分配在執(zhí)行階段完成,全局執(zhí)行上下文如下:

GlobalExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
          Type: "Object",
          // Identifier bindings go here
          a: 20,
          b: 30,
          multiply: < func >
        }
        outer: <null>,
        ThisBinding: <Global Object>
      },
    VariableEnvironment: {
        EnvironmentRecord: {
          Type: "Object",
          // Identifier bindings go here
          c: undefined,
        }
        outer: <null>,
        ThisBinding: <Global Object>
      }
    }
}

當(dāng)執(zhí)行函數(shù) multiply(20, 30),就會創(chuàng)建一個新的函數(shù)執(zhí)行上下文來執(zhí)行函數(shù)代碼:

FunctionExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
          Type: "Declarative",
          // Identifier bindings go here
          Arguments: {0: 20, 1: 30, length: 2},
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>,
      },
    VariableEnvironment: {
        EnvironmentRecord: {
          Type: "Declarative",
          // Identifier bindings go here
          g: undefined
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>
      }
    }
}

之后執(zhí)行上下文來到 執(zhí)行階段,這意味著已完成對函數(shù)內(nèi)部變量的分配。

FunctionExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
          Type: "Declarative",
          // Identifier bindings go here
          Arguments: {0: 20, 1: 30, length: 2},
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>,
      },
    VariableEnvironment: {
        EnvironmentRecord: {
          Type: "Declarative",
          // Identifier bindings go here
          g: 20
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>
      }
    }
}

函數(shù)執(zhí)行之后,返回值存到了變量 c 中,隨后全局詞法環(huán)境被更新了,全局的代碼運(yùn)行完成,程序結(jié)束。

上面我們看到 letconst 聲明的變量并沒有被初始化,但是 var 定義的變量被設(shè)置為 undefined(這對應(yīng)了 letconst 沒有變量提示這一特性)。

這是因為在創(chuàng)建階段,Javascript 引擎讀取了所有的代碼,找到了變量和函數(shù)的定義,其中函數(shù)聲明被完整的存在了環(huán)境中,而變量被初始化稱 undefined (如果是 var 聲明的變量)或者保持未初始化(如果是 letconst 聲明的變量)。

這就是為什么 var 聲明的變量可以在聲明之前被訪問到,而 letconst 聲明的變量不允許被訪問(reference error)。

這就是常說的 變量提升(varaible hoisting) 。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容