JavaScript 中的执行上下文

126 阅读7分钟

执行上下文

概念

执行上下文:评估和执行 JavaScript 代码环境的抽象概念。每次 JavaScript 执行的时候都是在执行上下文中执行的。

分类

全局执行上下文:默认或是基础的上下文。任何不在函数内部执行的代码,他的执行环境都是全局上下文。它会执行两件事:创建一个全局的环境(在浏览器的情况下是:windows对象),并将this指向这个全局对象。一个程序只会有一个全局执行上下文。

函数执行上下文:每次函数调用的时候都会为其创建一个新的上下文环境。每一个函数都有一个属于自己的上下文环境,函数上下文可以有多个。每当一个新的执行上下文被创建的时候。它会按照定义的顺序进行执行。

Eval 函数执行上下文:执行在Eval 函数中的代码也拥有自己的上下文。但是JavaScript并不常使用。这个情况可以先忽略。

执行栈

概念

执行栈:也就是其他编程语言说的“调用栈”,是一种LIFO(后进先出)数据结构的栈,用来存储代码运行时所有的上下文环境。

当开始执行代码的时候,第一件事情时先创建一个全局的作用域,并且将其压入栈中,每遇到一个新的函数的时候都会创建一个新的函数执行上下文,然后压入栈中。

管理执行上下文

image.png

让我们以一段代码为例

let a = "哈哈哈哈"

const first =()=>{
  console.log("inside first function");
  second();
  console.log("again inside first function");
}

const second =()=>{
   console.log("inside second function");
}

fist();

console.log('Inside Global Execution Context');

image.png

现在让我们解析一下,为什么执行上下文栈是图中的样子呢?

首先我们明确一下:什么时候会创建一个新的执行上下文 -- 在函数执行的时候

  1. 首先我们在程序执行的时候,会先创建一个“全局上下文”,并将其压入栈中。

  2. 我们发现调用了first方法,即first(),然后我们为first创建了一个新的执行上下文,并将其压入当前栈的顶部。

  3. 在执行 first()方法的时候,发现其内部调用了second()方法,然后我们为second创建了一个新的执行上下文,并将其压入当前栈的顶部。

  4. 执行完成 second()后,second()方法的执行上下文会从栈中弹出来,并销毁了这个执行上下文。然后控制流指向 first()的执行上下文。

  5. 执行完成 first()后,second()方法的执行上下文会从栈中弹出来,并销毁了这个执行上下文。。然后控制流指向全局上下文。

  6. 一旦所有代码执行完毕,JavaScript 引擎从当前栈中移除全局执行上下文。

创建执行上下文

创建执行上下文有两个阶段:1) 创建阶段 和 2) 执行阶段。

创建阶段

JavaScript执行需要在执行上下文中执行,所以需要在执行前就要创建好执行上下文。当我们创建执行上下文的时候会发生三件事情。

  • this 的指向问题
  • 创建词法环境组件
  • 创建变量环境组件

this绑定

  • 在全局上下文中,this指向的是全局对象。(eg:浏览器中指向的是windows)
  • 在函数指向的是调用方,谁调用了这个函数,那么this就指向了谁。如果它被一个引用对象调用,那么 this 会被设置成那个对象,否则 this 的值被设置为全局对象或者 undefined(在严格模式下)。
const foo = {
  baz:()=>{
    console.log(this);
  }
}

foo.baz(); // this指向foo,因为是foo调用了baz

const bar = foo.baz;

bar(); // bar 的this 指向全局上下文,即window对象

词法环境

词法环境是ECMA中的一个规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符和具体变量和函数的关联。简单来说,就是建立了标识符-变量映射表。这里的标识符指的是变量名称或函数名称,而变量是实际变量原始值或事对象/函数的引用地址。

词法环境的类型

  1. 全局环境(global environment):是一个没有外部环境的词法环境。外部环境引用为 null,全局环境的记录可以绑定变量,并关联对象的全局对象。

  2. 模块环境 (module environment):包含顶级声明的绑定,也包含模块显式导入的绑定,外部环境是全局环境。

  3. 函数环境(function environment):函数环境也是一个对应于ECMAScript函数对象调用的词法环境。函数环境可以建立新的此绑定。函数环境还支持super调用所需的状态。

词法环境有两个组成部分:

  1. 环境记录:存储变量和函数声明的实际位置

  2. 对外部环境的引用:实际上就是对外部或者说是父级词法环境的引用。这对理解闭包是如何工作的尤为重要。

  3. this绑定

我们用例子来理解一下

lexicalEnvironment = {
  environmentRecord:{ // 环境记录
    <identifier>:value,
    <identifier>:value,
  }
  outer:<Reference to the parent lexical environment > // 外部环境引用
}

实际的例子

const text ="lexical environment";

function fn(){
  const fnText="inside lexical environment";
  console.log("Inside function);
}

fn();

console.log('inside global execution context');

上面的例子对应的词法环境:

// 全局的词法环境
globalLexicalEnvironment = {
  environmentRecord:{ 
   text : "lexical environment",
   fn : <reference to fn object>,
  }
  outer : null
}

而当 JavaScript 引擎为函数 fn 创建执行上下文时,它会再创建一个词法环境来存储在函数执行期间在该函数内部定义的变量。函数 fn 的词法环境如下

// 函数的词法环境
functionLexicalEnvironment = {
  environmentRecord:{ 
   fnText : "inside lexical environment"
  }
  outer : <globalLexicalEnvironment>
}

ps:当函数完成时,它的执行上下文将从堆栈中删除,但它的词法环境就不一定了,可能会从内存中删除,也可能不会从内存中删除,这取决于该词法环境是否被其外部词法环境属性中的任何其他词法环境引用。典型的例子就是闭包。

综上所述:词法就是JavaScript 引擎创建一个执行上下文的时候,用来储存变量和函数声明的环境

变量环境

变量环境也是一个词法环境,所以它有着上面定义的词法环境的所有属性。在ES6中,词法环境和变量环境二者的区别,词法环境同来存储函数声明和变量(let 和 const)绑定,变量环境只能用来存储var 变量绑定。

变量环境本质上仍是词法环境,但它只存储var声明的变量,这样在初始化变量时可以赋值为undefined。

用例子看一下这个过程

var global_variable1 = 'hello';
let global_variable2 = 'world';

function fn(){
  var inside_variable1 = 'fn';
  let inside_variable2 = 'function';
  {
    var block_variable1 = 'var_block';
    let block_variable1 = 'let_block';
  }
}

fn()

实际代码的执行顺序:

// 全局
var global_variable1
fn;
global_variable1 = 'hello';
let global_variable2 = 'world';
function fn(){}
fn()

// 函数内部
 var inside_variable1;
 var block_variable1;
 inside_variable1 = 'fn';
 let inside_variable2 = 'function';
 {
   block_variable1 = 'var_block';
   let block_variable1 = 'let_block';
 }

假设我们要查找全局变量 global_variable1,从fn 向上查找,直到找到为止

fn 的词法环境 -> fn 的变量环境 -> 全局词法环境  -> 全局变量环境 

最后使用网上的一个例子最后检查一下,用于自查学习进度,无讲解

let a = 10;                                         
const b = 20;                                       
var sum;                                              
                                                       
function add(e, f){                                  
    var d = 40;                                 
    return d + e + f                         
}                                                      
                                                       
let utils = {                                       
    add                                            
}                                                      
sum = utils.add(a, b);
GlobalExecutionContext ={
  LexicalEnvironment:{
    EnvironmentRecord:{
      type: 'object',       
      add:<function>,
      a:<uninitialied>,
      b:<uninitialied>,
      utils:<uninitialied>,
    },
    outer:null,
    this:<globalObject>
  },
  VariableEnvironment:{
     EnvironmentRecord:{
      sum:undefined
     },
    outer:null,
    this:<globalObject>
  }
}

// 当运行到函数add时才会创建函数执行上下文                             
FunctionExecutionContext ={
  LexicalEnvironment:{
    EnvironmentRecord:{
       type: 'declarative',
        arguments: {0: 10, 1: 20, length: 2},
        [[ThisValue]]: <utils>,                         
        [[NewTarget]]: undefined,           
    },
     outer: <GlobalLexicalEnvironment>,                      
     this: <utils>  
  },
  VariableEnvironment:{
     EnvironmentRecord:{
       type: 'declarative',
      d: undefined     
     },
     outer: <GlobalLexicalEnvironment>,                      
     this: <utils>  
  }
}

引用

blog.csdn.net/qq_42345237… zhuanlan.zhihu.com/p/159369973