1. 程式人生 > 其它 >建立上下文_一篇文章看懂js上下文

建立上下文_一篇文章看懂js上下文

技術標籤:建立上下文

前言:

我們都知道,JS程式碼的執行順序總是與程式碼先後順序有所差異,當先拋開非同步問題你會發現就算是同步程式碼,它的執行也與你的預期不一致,比如:

function f1() {    console.log('聽風是風');};f1(); //echofunction f1() {    console.log('echo');};f1(); //echo

按照程式碼書寫順序,應該先輸出 聽風是風,再輸出 echo才對,很遺憾,兩次輸出均為 echo;如果我們將上述程式碼中的函式宣告改為函式表示式,結果又不太一樣:

var f1 = function () {    console.log('聽風是風');};f1(); //聽風是風var f1 = function() {    console.log('echo');};f1(); //echo

這說明程式碼在執行前一定發生了某些微妙的變化,JS引擎究竟做了什麼呢?這就不得不提JS執行上下文的了。

JS執行上下文

JS程式碼在執行前,JS引擎總要做一番準備工作,這份工作其實就是建立對應的執行上下文;

執行上下文有且只有三類,全域性執行上下文,函式上下文,與eval上下文;由於eval一般不會使用,這裡不做討論。

1.全域性執行上下文

全域性執行上下文只有一個,在客戶端中一般由瀏覽器建立,也就是我們熟知的window物件,我們能通過this直接訪問到它。

b2c6cf3b4d67b7c1121ff8c324744701.png

全域性物件window上預定義了大量的方法和屬性,我們在全域性環境的任意處都能直接訪問這些屬性方法,同時window物件還是var宣告的全域性變數的載體。我們通過var建立的全域性物件,都可以通過window直接訪問。

4973c4aa414d0a6a2fc29584118e24b3.png

2.函式執行上下文

函式執行上下文可存在無數個,每當一個函式被呼叫時都會建立一個函式上下文;需要注意的是,同一個函式被多次呼叫,都會建立一個新的上下文。

說到這你是否會想,上下文種類不同,而且建立的數量還這麼多,它們之間的關係是怎麼樣的,又是誰來管理這些上下文呢,這就不得不說說執行上下文棧了。

執行上下文棧(執行棧)

執行上下文棧(下文簡稱執行棧)也叫呼叫棧,執行棧用於儲存程式碼執行期間建立的所有上下文,具有LIFO(Last In First Out後進先出,也就是先進後出)的特性。

JS程式碼首次執行,都會先建立一個全域性執行上下文並壓入到執行棧中,之後每當有函式被呼叫,都會建立一個新的函式執行上下文並壓入棧內;由於執行棧LIFO的特性,所以可以理解為,JS程式碼執行完畢前在執行棧底部永遠有個全域性執行上下文。

function f1() {    f2();    console.log(1);};function f2() {    f3();    console.log(2);};function f3() {    console.log(3);};f1();//3 2 1

我們通過執行棧與上下文的關係來解釋上述程式碼的執行過程,為了方便理解,我們假象執行棧是一個數組,在程式碼執行初期一定會建立全域性執行上下文並壓入棧,因此過程大致如下:

//程式碼執行前建立全域性執行上下文ECStack = [globalContext];// f1呼叫ECStack.push('f1 functionContext');// f1又呼叫了f2,f2執行完畢之前無法console 1ECStack.push('f2 functionContext');// f2又呼叫了f3,f3執行完畢之前無法console 2ECStack.push('f3 functionContext');// f3執行完畢,輸出3並出棧ECStack.pop();// f2執行完畢,輸出2並出棧ECStack.pop();// f1執行完畢,輸出1並出棧ECStack.pop();// 此時執行棧中只剩下一個全域性執行上下文

那麼到這裡,我們解釋了執行棧與執行上下文的儲存規則;還記得我在前文提到程式碼執行前JS引擎會做準備建立執行上下文嗎,具體怎麼建立呢,我們接著說。

執行上下文建立階段

執行上下文建立分為建立階段與執行階段兩個階段,較為難理解應該是建立階段,我們先說建立階段。

JS執行上下文的建立階段主要負責三件事:確定this---建立詞法環境元件(LexicalEnvironment)---建立變數環境元件(VariableEnvironment)

這裡我就直接借鑑了他人翻譯資料的虛擬碼,來表示這個建立過程:

ExecutionContext = {      // 確定this的值    ThisBinding = <this value>,    // 建立詞法環境元件    LexicalEnvironment = {},    // 建立變數環境元件    VariableEnvironment = {},};

如果你有閱讀其它關於執行上下文的文章讀到這裡一定有疑問,執行上下文建立過程不是應該解釋this,作用域與變數物件/活動物件才對嗎,怎麼跟別的地方說的不一樣,這點我後面解釋。

1.確定this

官方的稱呼為This Binding,在全域性執行上下文中,this總是指向全域性物件,例如瀏覽器環境下this指向window物件。

而在函式執行上下文中,this的值取決於函式的呼叫方式,如果被一個物件呼叫,那麼this指向這個物件。否則this一般指向全域性物件window或者undefined(嚴格模式)。

2.詞法環境元件

詞法環境是一個包含識別符號變數對映的結構,這裡的識別符號表示變數/函式的名稱,變數是對實際物件【包括函式型別物件】或原始值的引用。

詞法環境由環境記錄與對外部環境引入記錄兩個部分組成。

其中環境記錄用於儲存當前環境中的變數和函式宣告的實際位置;外部環境引入記錄很好理解,它用於儲存自身環境可以訪問的其它外部環境,那麼說到這個,是不是有點作用域鏈的意思?

我們在前文提到了全域性執行上下文與函式執行上下文,所以這也導致了詞法環境分為全域性詞法環境與函式詞法環境兩種。

全域性詞法環境元件:

對外部環境的引入記錄為null,因為它本身就是最外層環境,除此之外它還記錄了當前環境下的所有屬性、方法位置。

函式詞法環境元件:

包含了使用者在函式中定義的所有屬性方法外,還包含了一個arguments物件。函式詞法環境的外部環境引入可以是全域性環境,也可以是其它函式環境,這個根據實際程式碼而來。

這裡借用譯文中的虛擬碼(環境記錄在全域性和函式中也不同,全域性中的環境記錄叫物件環境記錄,函式中環境記錄叫宣告性環境記錄,說多了糊塗,下方有展示):

// 全域性環境GlobalExectionContext = {    // 全域性詞法環境    LexicalEnvironment: {        // 環境記錄        EnvironmentRecord: {            Type: "Object", //型別為物件環境記錄            // 識別符號繫結在這裡         },        outer: < null >    }};// 函式環境FunctionExectionContext = {    // 函式詞法環境    LexicalEnvironment: {        // 環境紀錄        EnvironmentRecord: {            Type: "Declarative", //型別為宣告性環境記錄            // 識別符號繫結在這裡         },        outer: < Global or outerfunction environment reference >    }};

3.變數環境元件

變數環境可以說也是詞法環境,它具備詞法環境所有屬性,一樣有環境記錄與外部環境引入。在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 = {    // this繫結為全域性物件    ThisBinding: Object>,    // 詞法環境    LexicalEnvironment: {          //環境記錄      EnvironmentRecord: {          Type: "Object",  // 物件環境記錄        // 識別符號繫結在這裡 let const建立的變數a b在這        a: < uninitialized >,          b: < uninitialized >,          multiply: < func >        }      // 全域性環境外部環境引入為null      outer: <null>      },      VariableEnvironment: {        EnvironmentRecord: {          Type: "Object",  // 物件環境記錄        // 識別符號繫結在這裡  var建立的c在這        c: undefined,        }      // 全域性環境外部環境引入為null      outer: <null>      }    }  // 函式執行上下文  FunctionExectionContext = {     //由於函式是預設呼叫 this繫結同樣是全域性物件    ThisBinding: Object>,    // 詞法環境    LexicalEnvironment: {        EnvironmentRecord: {          Type: "Declarative",  // 宣告性環境記錄        // 識別符號繫結在這裡  arguments物件在這        Arguments: {0: 20, 1: 30, length: 2},        },        // 外部環境引入記錄為      outer:     },      VariableEnvironment: {        EnvironmentRecord: {          Type: "Declarative",  // 宣告性環境記錄        // 識別符號繫結在這裡  var建立的g在這        g: undefined        },        // 外部環境引入記錄為      outer:     }    }

不知道你有沒有發現,在執行上下文建立階段,函式宣告與var宣告的變數在建立階段已經被賦予了一個值,var宣告被設定為了undefined,函式被設定為了自身函式,而let const被設定為未初始化。

現在你總知道變數提升與函式宣告提前是怎麼回事了吧,以及為什麼let const為什麼有暫時性死域,這是因為作用域建立階段JS引擎對兩者初始化賦值不同。

上下文除了建立階段外,還有執行階段,這點大家應該好理解,程式碼執行時根據之前的環境記錄對應賦值,比如早期var在建立階段為undefined,如果有值就對應賦值,像let const值為未初始化,如果有值就賦值,無值則賦予undefined。

關於變數物件與活動物件

回答前面的問題,為什麼別人的博文介紹上下文都是談作用域,變數物件和活動物件,我這就成了詞法環境,變數環境了。

我在閱讀相關資料也產生了這個疑問,一番查閱可以確定的是,變數物件與活動物件的概念是ES3提出的老概念,從ES5開始就用詞法環境和變數環境替代了,因為更好解釋。

在上文中,我們通過介紹詞法環境與變數環境解釋了為什麼var會存在變數提升,為什麼let const沒有,而通過變數物件與活動物件是很難解釋的,由其是在JavaScript在更新中不斷在彌補當初設計的坑。

其次,詞法環境的概念與變數物件這類概念也是可以對應上的。

我們知道變數物件與活動物件其實都是變數物件,變數物件是與執行上下文相關的資料作用域,儲存了在上下文中定義的變數和函式宣告。而在函式上下文中,我們用活動物件(activation object, AO)來表示變數物件。

那這不正好對應到了全域性詞法記錄與函式詞法記錄了嗎。而且由於ES6新增的let const不存在變數提升,於是正好有了詞法環境與變數環境的概念來解釋這個問題。

所以說到這,你也不用為詞法環境,變數物件的概念鬧衝突了。

我們來總結下上面提到的概念。

總結

1.全域性執行上下文一般由瀏覽器建立,程式碼執行時就會建立;函式執行上下文只有函式被呼叫時才會建立,呼叫多少次函式就會建立多少上下文。

2.呼叫棧用於存放所有執行上下文,滿足FILO規則。

3.執行上下文建立階段分為繫結this,建立詞法環境,變數環境三步,兩者區別在於詞法環境存放函式宣告與const let宣告的變數,而變數環境只儲存var宣告的變數。

4.詞法環境主要由環境記錄與外部環境引入記錄兩個部分組成,全域性上下文與函式上下文的外部環境引入記錄不一樣,全域性為null,函式為全域性環境或者其它函式環境。環境記錄也不一樣,全域性叫物件環境記錄,函式叫宣告性環境記錄。

5.你應該明白了為什麼會存在變數提升,函式提升,而let const沒有。

6.ES3之前的變數物件與活動物件的概念在ES5之後由詞法環境,變數環境來解釋,兩者概念不衝突,後者理解更為通俗易懂。

不得不說相關文章也是看的我心累,也希望對有緣的你有所幫助,那麼到這裡,本文結束。

原文:https://www.cnblogs.com/echolun/p/11438363.html