瞭解Javascript中的執行上下文和執行堆棧

By Sukhjinder Arora | Aug 28, 2018

原文javascript

若是你是或者你想要成爲一名js開發者,那麼你必須瞭解js程序內部的運做。理解執行上下文和執行堆棧對於理解js的其它概念(如提高,範圍和閉包)相當重要。java

正確地理解執行上下文和執行堆棧將幫助你更好地使用js開發應用。es6

廢話少說,讓咱們開始吧:)數組

* * *

什麼是執行上下文?

簡單來講,執行上下文是預估和執行當前環境下js代碼的抽象概念。每當在js中運行代碼時,它都在執行上下文中運行。瀏覽器

(譯者:emmm,就是執行上下文包含了追蹤當前正在執行的代碼的所有狀態。)數據結構

執行上下文的類型

在js中有三種執行類型閉包

  • 全局執行上下文——這是默認或者說基礎執行上下文。函數外的代碼就處於全局執行上下文中。它作了兩件事:它建立了window對象(在瀏覽器環境下),也就是全局對象。並把this指向全局對象。在程序裏面只能有一個全局上下文。ide

  • 函數執行上下文——每次函數被調用,都會爲這個函數建立一個新的上下文。每一個函數都有本身的上下文,可是隻有被調用的時候纔會被建立。能夠有不少個函數執行上下文。每當建立一個新的函數執行上下文,js引擎都會按照定義好的順序執行一系列的步驟,我將會在下文中討論。函數

  • Eval 函數的執行上下文——eval函數執行的時候也會爲它裏面的代碼建立上下文,可是這個方法用的少,在本文略過。this

執行堆棧

執行堆棧在其它語言中被稱爲「調用棧」,是一種先進後出的一種數據結構,在代碼執行期間被用於存儲全部的執行上下文。

當js引擎開始解析js代碼時,會先建立全局執行上下文而且放在當前執行堆棧中。每當引擎遇到函數調用的代碼時,都會建立該函數的上下文並推入當前執行堆棧中。

引擎執行位於執行堆棧頂部的方法。當方法執行完畢,執行堆棧pop掉最頂部的上下文,接着引擎繼續執行堆棧頂部的方法。

用代碼示範一下:

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');
複製代碼

上述代碼的執行上下文堆棧。

當上述代碼在瀏覽器內加載,js引擎就會建立一個全局執行上下文而且把它推入當前執行堆棧中。當調用first()時,js爲該函數建立一個新的執行上下文,而且把它推入到當前執行堆棧。

當second()方法被first()調用,js引擎爲該方法建立一個新的執行上下文並把它推入當前執行堆棧。當second()執行完畢,這個方法的上下文就被執行堆棧推出,而且執行下一個執行上下文,也就是first()。

當first()執行完畢,重複以上步驟。一旦執行了全部代碼,JavaScript引擎就會從當前堆棧中刪除全局執行上下文。

執行上下文如何建立?

直到如今,咱們已經知道js引擎如何管理執行上下文的了,如今讓咱們瞭解下執行上下文如何被js建立的。

建立執行上下文有兩個階段:1)建立階段,2)執行階段(譯者:???懵逼臉)。

建立階段

在執行任何JavaScript代碼以前,執行上下文將經歷建立階段。在建立階段會發生三件事:

  1. this綁定
  2. 詞法環境(LexicalEnvironment)建立
  3. 變量環境(VariableEnvironment)建立

(譯者:VariableEnvironment和LexicalEnvironment譯者也是第一次聽到,慚愧,大學沒學過編譯原理,在js中還有個this綁定,彷佛是js特有)

所以,執行上下文在概念上能夠這樣表示,以下:

ExecutionContext = {
  ThisBinding = <this value>,
  LexicalEnvironment = { ... },
  VariableEnvironment = { ... },
}
複製代碼

this綁定

在全局執行上下文中,this值指向全局對象(在瀏覽器內是window對象)。

在函數執行上下文中,this的值取決於函數的調用的時候的狀況。若是它由對象引用調用,this值就是該對象,不然this值指向全局或者爲undefined(在嚴格模式下)。

例如:

let 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
let calculateAge = person.calcAge;
calculateAge();
// 'this' refers to the global window object, because no object reference was given
複製代碼

詞法環境

官方es6文檔對詞法環境有以下解釋:

詞彙環境是一種規範類型,用於根據ECMAScript代碼的詞法嵌套結構定義標識符與特定變量和函數的關聯。詞彙環境由environment record(譯者:實在不知道咋翻)和外部詞彙環境的可能爲null的引用組成。

(譯者:硬翻的,有點怪)

簡而言之,詞彙環境是一種包含標識符變量映射的結構(此處標識符指的是變量/函數的名稱,變量是對實際對象【包括函數類型的對象】或原始值的引用)。

如今,詞法環境由兩部分組成:

(1)environment record

(2)外部環境的引用

一、environment record是存放變量和函數聲明的一個地方

二、對外部環境的引用意味着它能夠訪問其外部詞彙環境。

有兩種類型的詞法環境:

  • 一種是全局環境(在全局執行上下文裏),它沒有外部環境的引用。它的外部環境引用爲null。它有全局對象(window對象)和關聯的方法和屬性(例如數組方法),以及任何用戶定義的全局變量,而且this的值指向全局對象。

  • 一種是函數環境,它存放用戶在函數裏定義的變量。而且外部環境能夠指向全局環境,或者是外層函數環境。

筆記——對於函數環境,environment record還包含一個arguments對象,該對象包含索引和傳遞給函數的參數之間的映射以及傳遞給函數的參數的長度(數量)。例如,下面函數的參數對象以下所示:

function foo(a, b) {
  var c = a + b;
}
foo(2, 3);
// argument object
Arguments: {0: 2, 1: 3, length: 2},
複製代碼

environment record也有兩種類型:

  • 聲明性environment record存儲變量,函數和參數。 一個函數環境包含聲明性environment record。

  • 對象environment record用於定義在全局執行上下文中出現的變量和函數的關聯。全局環境包含對象environment record。

抽象地說,詞法環境在僞代碼中看起來像這樣:

GlobalExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
    }
    outer: <null>
  }
}
FunctionExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
    }
    outer: <Global or outer function environment reference>
  }
}
複製代碼

變量環境

它也是一個詞法環境,其EnvironmentRecord包含由此執行上下文中的VariableStatements建立的綁定。

如上所述,變量環境也是一個詞彙環境,所以它具備上面定義的詞法環境的全部屬性。

在es6,詞法環境和變量環境的不一樣在於前者用於存儲函數聲明和變量(let和const)綁定,然後者用於存儲變量(var)的綁定。

來看個例子:

let a = 20;
const b = 30;
var c;
function multiply(e, f) {
 var g = 20;
 return e * f * g;
}
c = multiply(20, 30);
複製代碼

而後執行上下文會像這樣:

GlobalExectionContext = {
  ThisBinding: <Global Object>,
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: < func >
    }
    outer: <null>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      c: undefined,
    }
    outer: <null>
  }
}
FunctionExectionContext = {
 
  ThisBinding: <Global Object>,
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      Arguments: {0: 20, 1: 30, length: 2},
    },
    outer: <GlobalLexicalEnvironment>
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      g: undefined
    },
    outer: <GlobalLexicalEnvironment>
  }
}
複製代碼

筆記——函數multiply調用時纔會建立函數執行上下文。

正如你所注意到的同樣,let和const定義的變量沒有綁定任何值,但var定義的變量爲undefined

這是由於在建立階段,掃描代碼尋找變量和函數聲明時,函數聲明徹底存儲在環境中,但變量最初設置爲undefined(var)或保持爲爲初始化(let、const)。

(譯者:就是var會聲明提高,而let和const不會)

這就是爲何你能夠在變量聲明前訪問到var定義的變量,而訪問let和const定義的變量則會拋出引用錯誤。

這就是js的變量提高。

執行階段

這是整篇文章中最簡單的部分。 在此階段,完成對全部這些變量的分配,最後執行代碼。

筆記——在執行階段,若是js引擎在源代碼聲明的實際位置找不到let變量的值,那麼它將爲其分配undefined值。

結論

如今,咱們已經了js的部分執行原理,雖然理解了這些概念不必定能讓你成爲出色的js開發者,可是明白了上述的概念能讓你更好理解js的其它概念,例如變量提高、閉包。

相關文章
相關標籤/搜索