JavaScript進階-執行上下文(理解執行上下文一篇就夠了)

前言

在編程這個行業中老是能聽到這個詞執行上下文。那麼什麼叫執行上下文呢?javascript

本篇文章主要是介紹javascript中的執行上下文, 看完以後你能夠了解到:html

  • 執行上下文的類型
  • 執行上下文特色
  • 執行棧
  • 執行上下文的生命週期

概念

首先咱們來介紹什麼是「執行上下文」.前端

舉個例子,生活中,相同的話在不一樣的場合說可能會有不一樣的意思,而這個說話的場合就是咱們說話的語境。java

一樣對應在編程中, 對程序語言進行「解讀」的時候,也必須在特定的語境中,這個語境就是javascript中的執行上下文。node

一句話歸納:編程

執行上下文就是javascript代碼被解析和執行時所在環境的抽象概念。數組

執行上下文的類型

js中,執行上下文分爲如下三種:瀏覽器

  • 全局執行上下文:只有一個,也就是瀏覽器對象(即window對象),this指向的就是這個全局對象。
  • 函數執行上下文:有無數個,只有在函數被調用時纔會被建立,每次調用函數都會建立一個新的執行上下文。
  • Eval函數執行上下文jseval函數執行其內部的代碼會建立屬於本身的執行上下文, 不多用並且不建議使用。

執行上下文的特色

  1. 單線程,只在主線程上運行;
  2. 同步執行,從上向下按順序執行;
  3. 全局上下文只有一個,也就是window對象;
  4. 函數執行上下文沒有限制;
  5. 函數每調用一次就會產生一個新的執行上下文環境。

JS如何管理多個執行上下文

經過上面介紹,咱們知道了js代碼在運行時可能會產生無數個執行上下文,那麼它是如何管理這些執行上下文的呢?函數

同時因爲js是單線程的,因此不能同時幹兩件事,必須一個個去執行,那麼這麼多的執行上下文是按什麼順序執行的呢?ui

執行棧

接下來就對上面的問題作出解答,管理多個執行上下文靠的就是執行棧,也被叫作調用棧

特色:後進先出(LIFO)的結構。

做用:存儲在代碼執行期間的全部執行上下文。

LIFO: last-in, first-out,相似於向乒乓球桶中放球,最早放入的球最後取出)

js在首次執行的時候,會建立一個全局執行上下文並推入棧中。

每當有函數被調用時,引擎都會爲該函數建立一個新的函數執行上下文而後推入棧中。

當棧頂的函數執行完畢以後,該函數對應的執行上下文就會從執行棧中pop出,而後上下文控制權移到下一個執行上下文。

好比下面的一個例子🌰:

var a = 1; // 1. 全局上下文環境
function bar (x) {
    console.log('bar')
    var b = 2;
    fn(x + b); // 3. fn上下文環境
}
function fn (c) {
    console.log(c);
}
bar(3); // 2. bar上下文環境
複製代碼

以下圖:

context1

執行上下文的生命週期

執行上下文的生命週期也很是容易理解, 分爲三個階段:

  1. 建立階段
  2. 執行階段
  3. 銷燬階段

建立階段

建立階段, 主要有是有這麼幾件事:

  1. 肯定this的值, 也就是綁定this (This Binding);
  2. 詞法環境(LexicalEnvironment)組件被建立;
  3. **變量環境(VariableEnvironment)**組件被建立.

一張圖方便你理解 🤔

executionContext1

有一些教材中也喜歡用僞代碼來實現:

ExecutionContext = {  
  ThisBinding = <this value>, // 肯定this LexicalEnvironment = { ... }, // 詞法環境 VariableEnvironment = { ... }, // 變量環境 } 複製代碼

This Binding

經過上面的介紹咱們知道實際開發主要用到兩種執行上下文爲全局函數, 那麼綁定this在這兩種上下文中也不一樣.

  • 全局執行上下文中, this指的就是全局對象, 瀏覽器環境指向window對象, nodejs中指向這個文件的module對象.
  • 函數執行上下文較爲複雜, this的值取決於函數的調用方式. 具體有: 默認綁定、隱式綁定、顯式綁定、new綁定、箭頭函數.

詞法環境

如上圖, 詞法環境是由兩個部分組成的:

  1. 環境記錄: 存儲變量和函數聲明的實際位置;
  2. 對外部環境的引用: 用於訪問其外部詞法環境.

一樣的, 詞法環境也主要有兩種類型:

  1. 全局環境: 擁有一個全局對象(window對象)及其關聯的全部屬性和方法(好比數組的方法splice、concat等), 同時也包含了用戶自定義的全局變量. 可是全局環境中沒有外部環境的引用, 也就是外部環境引用爲null.
  2. 函數環境: 用戶在函數中自定義的變量和函數存儲在環境記錄中, 包含了arguments對象. 而對外部環境的引用能夠是全局環境, 也能夠是另外一個函數環境(好比一個函數中包含了另外一個函數).

繼續用僞代碼來實現:

GlobalExectionContext = { // 全局執行上下文
    LexicalEnvironment: {   // 詞法環境
        EnvironmentRecord: {   // 環境記錄
            Type: "Object"       // 全局環境
            // 標識符綁定在這裏
        },
        outer: <null> // 外部環境引用 } } FunctionExectionContext = { // 函數執行上下文 LexicalEnvironment: { // 詞法環境 EnvironmentRecord: { // 環境記錄 Type: "Object", // 函數環境 // 標識符綁定在這裏 }, outer: < Global or FunctionEnvironment> // 外部環境引用 } } 複製代碼

變量環境

變量環境其實也是一個詞法環境, 所以它具備上面定義的詞法環境的全部屬性.

在 ES6 中,詞法 環境和 變量 環境的區別在於前者用於存儲**函數聲明和變量( letconst綁定,然後者僅用於存儲變量( var )**綁定。

案例🌰:

var a;
var b = 1;
let c = 2;
const d = 3;
function fn (e, f) {
    var g = 4;
    return e + f + g;
}
a = fn(10, 20);
複製代碼

執行上下文以下:

GlobalExectionContext = { // 全局執行上下文
    ThisBinding: <Global Object>,
    LexicalEnvironment: {   // 詞法環境
    	EnvironmentRecord: {   // 環境記錄
        	Type: "Object",       // 全局環境
        	c: < uninitialized >,
                d: < uninitialized >,
        	fn: < func >
    	},
    	outer: <null>            // 外部環境引用
    },
    VariableEnvironment: {   // 變量環境
    	EnvironmentRecord: {   // 環境記錄
    		Type: "Object",
    		a: < uninitialized >,
    		b: < uninitialized >
    	},
    	outer: <null>  
    }
}
FunctionExectionContext = { // 函數執行上下文
    ThisBinding: <Global Object>, // this綁定window, 由於調用fn的是window對象
    LexicalEnvironment: {   // 詞法環境
    	EnvironmentRecord: {   // 環境記錄
    		Type: "Object",       // 函數環境
    		Arguments: { 0: 10, 1: 20, length: 2 }
    	},
        outer: < GlobalLexicalEnvironment > // 全局環境的引用
    },
    VariableEnvironment: {   // 變量環境
    	EnvironmentRecord: {   // 環境記錄
    		Type: "Object",
    		g: < uninitialized >
    	},
    	outer: < GlobalLexicalEnvironment > // 全局環境的引用
    }
}
複製代碼

所以咱們能夠知道變量提高的緣由是:

在建立階段,函數聲明存儲在環境中,而變量會被設置爲 undefined(在 var 的狀況下)或保持未初始化(在 letconst 的狀況下)。因此這就是爲何能夠在聲明以前訪問 var 定義的變量(儘管是 undefined ),但若是在聲明以前訪問 letconst 定義的變量就會提示引用錯誤的緣由。這就是所謂的變量提高。

執行階段

執行階段主要作三件事情:

  1. 變量賦值
  2. 函數引用
  3. 執行其餘的代碼

注⚠️

若是 Javascript 引擎在源代碼中聲明的實際位置找不到 let 變量的值,那麼將爲其分配 undefined 值。

銷燬階段

執行完畢出棧,等待回收被銷燬

後語

該篇文章僅僅只是對執行上下文作一個入門程度的介紹, 後面會深刻介紹它.

參考文章:

木易楊前端進階-理解JavaScript 中的執行上下文和執行棧

JS執行上下文

相關文章
相關標籤/搜索