【進階1-1期】理解JavaScript 中的執行上下文和執行棧

木易楊說發表於2019-03-04

本期的主題是呼叫堆疊,本計劃一共28期,每期重點攻克一個面試重難點,如果你還不瞭解本進階計劃,文末點選檢視全部文章。

如果覺得本系列不錯,歡迎點贊、評論、轉發,您的支援就是我堅持的最大動力。


執行上下文是當前 JavaScript 程式碼被解析和執行時所在環境的抽象概念。

執行上下文的型別

執行上下文總共有三種型別

  • 全域性執行上下文:只有一個,瀏覽器中的全域性物件就是 window 物件,this 指向這個全域性物件。

  • 函式執行上下文:存在無數個,只有在函式被呼叫的時候才會被建立,每次呼叫函式都會建立一個新的執行上下文。

  • Eval 函式執行上下文: 指的是執行在 eval 函式中的程式碼,很少用而且不建議使用。

執行棧

執行棧,也叫呼叫棧,具有 LIFO(後進先出)結構,用於儲存在程式碼執行期間建立的所有執行上下文。

首次執行JS程式碼時,會建立一個全域性執行上下文並Push到當前的執行棧中。每當發生函式呼叫,引擎都會為該函式建立一個新的函式執行上下文並Push到當前執行棧的棧頂。

根據執行棧LIFO規則,當棧頂函式執行完成後,其對應的函式執行上下文將會從執行棧中Pop出,上下文控制權將移到當前執行棧的下一個執行上下文。

var 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');

// Inside first function
// Inside second function
// Again inside first function
// Inside Global Execution Context
複製程式碼

【進階1-1期】理解JavaScript 中的執行上下文和執行棧

執行上下文的建立

執行上下文分兩個階段建立:1)建立階段; 2)執行階段

建立階段

  • 1、確定 this 的值,也被稱為 This Binding

  • 2、LexicalEnvironment(詞法環境) 元件被建立。

  • 3、VariableEnvironment(變數環境) 元件被建立。

直接看虛擬碼可能更加直觀

ExecutionContext = {  
  ThisBinding = <this value>,     // 確定this 
  LexicalEnvironment = { ... },   // 詞法環境
  VariableEnvironment = { ... },  // 變數環境
}
複製程式碼
This Binding
  • 全域性執行上下文中,this 的值指向全域性物件,在瀏覽器中this 的值指向 window物件,而在nodejs中指向這個檔案的module物件。

  • 函式執行上下文中,this 的值取決於函式的呼叫方式。具體有:預設繫結、隱式繫結、顯式繫結(硬繫結)、new繫結、箭頭函式,具體內容會在【this全面解析】部分詳解。

詞法環境(Lexical Environment)

詞法環境有兩個組成部分

  • 1、環境記錄:儲存變數和函式宣告的實際位置

  • 2、對外部環境的引用:可以訪問其外部詞法環境

詞法環境有兩種型別

  • 1、全域性環境:是一個沒有外部環境的詞法環境,其外部環境引用為 null。擁有一個全域性物件(window 物件)及其關聯的方法和屬性(例如陣列方法)以及任何使用者自定義的全域性變數,this 的值指向這個全域性物件。

  • 2、函式環境:使用者在函式中定義的變數被儲存在環境記錄中,包含了arguments 物件。對外部環境的引用可以是全域性環境,也可以是包含內部函式的外部函式環境。

直接看虛擬碼可能更加直觀

GlobalExectionContext = {  // 全域性執行上下文
  LexicalEnvironment: {    	  // 詞法環境
    EnvironmentRecord: {   		// 環境記錄
      Type: "Object",      		   // 全域性環境
      // 識別符號繫結在這裡 
      outer: <null>  	   		   // 對外部環境的引用
  }  
}

FunctionExectionContext = { // 函式執行上下文
  LexicalEnvironment: {  	  // 詞法環境
    EnvironmentRecord: {  		// 環境記錄
      Type: "Declarative",  	   // 函式環境
      // 識別符號繫結在這裡 			  // 對外部環境的引用
      outer: <Global or outer function environment reference>  
  }  
}
複製程式碼
變數環境

變數環境也是一個詞法環境,因此它具有上面定義的詞法環境的所有屬性。

在 ES6 中,詞法 環境和 變數 環境的區別在於前者用於儲存**函式宣告和變數( letconst繫結,而後者僅用於儲存變數( 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",  
      // 識別符號繫結在這裡  
      a: < uninitialized >,  
      b: < uninitialized >,  
      multiply: < func >  
    }  
    outer: <null>  
  },

  VariableEnvironment: {  
    EnvironmentRecord: {  
      Type: "Object",  
      // 識別符號繫結在這裡  
      c: undefined,  
    }  
    outer: <null>  
  }  
}

FunctionExectionContext = {  
   
  ThisBinding: <Global Object>,

  LexicalEnvironment: {  
    EnvironmentRecord: {  
      Type: "Declarative",  
      // 識別符號繫結在這裡  
      Arguments: {0: 20, 1: 30, length: 2},  
    },  
    outer: <GlobalLexicalEnvironment>  
  },

  VariableEnvironment: {  
    EnvironmentRecord: {  
      Type: "Declarative",  
      // 識別符號繫結在這裡  
      g: undefined  
    },  
    outer: <GlobalLexicalEnvironment>  
  }  
}
複製程式碼

變數提升的原因:在建立階段,函式宣告儲存在環境中,而變數會被設定為 undefined(在 var 的情況下)或保持未初始化(在 letconst 的情況下)。所以這就是為什麼可以在宣告之前訪問 var 定義的變數(儘管是 undefined ),但如果在宣告之前訪問 letconst 定義的變數就會提示引用錯誤的原因。這就是所謂的變數提升。

執行階段

此階段,完成對所有變數的分配,最後執行程式碼。

如果 Javascript 引擎在原始碼中宣告的實際位置找不到 let 變數的值,那麼將為其分配 undefined 值。

參考

理解 Javascript 執行上下文和執行棧

進階系列目錄

  • 【進階1期】 呼叫堆疊
  • 【進階2期】 作用域閉包
  • 【進階3期】 this全面解析
  • 【進階4期】 深淺拷貝原理
  • 【進階5期】 原型Prototype
  • 【進階6期】 高階函式
  • 【進階7期】 事件機制
  • 【進階8期】 Event Loop原理
  • 【進階9期】 Promise原理
  • 【進階10期】Async/Await原理
  • 【進階11期】防抖/節流原理
  • 【進階12期】模組化詳解
  • 【進階13期】ES6重難點
  • 【進階14期】計算機網路概述
  • 【進階15期】瀏覽器渲染原理
  • 【進階16期】webpack配置
  • 【進階17期】webpack原理
  • 【進階18期】前端監控
  • 【進階19期】跨域和安全
  • 【進階20期】效能優化
  • 【進階21期】VirtualDom原理
  • 【進階22期】Diff演算法
  • 【進階23期】MVVM雙向繫結
  • 【進階24期】Vuex原理
  • 【進階25期】Redux原理
  • 【進階26期】路由原理
  • 【進階27期】VueRouter原始碼解析
  • 【進階28期】ReactRouter原始碼解析

交流

進階系列文章彙總:github.com/yygmind/blo…,內有優質前端資料,歡迎領取,覺得不錯點個star。

我是木易楊,網易高階前端工程師,跟著我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高階前端的世界,在進階的路上,共勉!

【進階1-1期】理解JavaScript 中的執行上下文和執行棧

相關文章