前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解执行上下文和执行栈

深入理解执行上下文和执行栈

作者头像
Chor
发布2019-11-07 18:55:25
6790
发布2019-11-07 18:55:25
举报
文章被收录于专栏:前端之旅前端之旅

执行上下文、执行栈、作用域链、闭包,这其实是一整套相关的东西,之前转载的文章也有讲到这些。下面两篇文章会更加详细地解释这些概念。

1.执行上下文

1.1 定义

执行上下文(execution context)是当前 JavaScript 代码被解析和执行时所在环境的抽象概念,

1.2 类型

  • 全局执行上下文 只有一个。它创建了一个全局对象(浏览器中是window对象),并将this指向该对象。
  • 函数执行上下文 无数个。每次调用函数时,都会为该函数创建一个新的执行上下文。
  • eval函数执行上下文 运行在 eval 函数中的代码也获得了自己的执行上下文,eval函数不常用,所以这里不讨论

2.执行栈

执行栈(execution stack),也即调用栈(call stack),具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。 当 JavaScript 引擎首次读取脚本时,它会创建一个全局执行上下文并将其push到当前的执行栈。每当调用函数的时候,都会为该函数创建一个新的执行上下文并将其push到栈顶;在函数执行完毕后,对应的执行上下文将会从栈顶pop出,上下文控制权将移到当前执行栈的下一个执行上下文。

代码语言:javascript
复制
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');

// Inside first function
// Inside second function
// Again inside first function
// Inside Global Execution Context

3.执行上下文的创建

执行上下文分两个阶段创建:1)创建阶段(The Creation Phase); 2)执行阶段(The Execution Phase)

3.1 创建阶段

  • 词法环境组件被创建
  • 变量环境组件被创建

用伪代码表示就是:

代码语言:javascript
复制
ExecutionContext = {  
  LexicalEnvironment = { ... },   // 词法环境
  VariableEnvironment = { ... },  // 变量环境
}

3.1.1 词法环境

词法环境(Lexical environment)是一个包含标识符变量映射的结构。(这里的标识符表示变量/函数的名称,变量是对实际对象【包括函数类型对象】或原始值的引用)

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

  • 环境记录:存储变量和函数声明的实际位置
  • 对外部环境的引用:可以访问其外部词法环境
  • this绑定:确定this的指向

词法环境有两种类型:

  • 全局环境:全局执行上下文的词法环境。
  • 函数环境:函数执行上下文的词法环境。
3.1.1.1 环境记录:

根据词法环境的两种类型,环境记录(Environment record)同样也有两种类型:

  • 对象环境记录(Object environment record): 全局环境的环境记录类型。存储全局变量和函数声明、全局对象(window 对象)和关联的属性/方法。
  • 声明性环境记录(Declarative environment record): 函数环境的环境记录类型。存储局部变量和函数声明、arguments对象。arguments对象包含了索引与参数之间的映射,以及传给函数的参数的个数。
代码语言:javascript
复制
function foo(a, b) {
  var c = a + b;
}
foo(2, 3);
// argument object
Arguments: {0: 2, 1: 3, length: 2},
3.1.1.2 外部环境引用:

外部环境引用(Reference to the outer environment)表明当前词法环境能够访问外部词法环境。这意味着如果JavaScript引擎未在当前词法环境找到变量,它将向外部词法环境寻找(这有点类似原型链中的属性查找)

全局环境没有外部环境,其外部环境引用为 null。

函数环境有外部环境,其外部环境引用可以是全局环境,也可以是包含内部函数的外部函数环境。

3.1.1.3 this绑定:

全局执行上下文中,this绑定(this binding)到全局对象(对于浏览器,该对象为window);函数执行上下文中,this绑定到谁将取决于函数的调用位置(或者说调用方法)。 我会在另一篇文章总结this的绑定机制,所以这里不再展开。

讲完了词法环境的三个组成部分,最后再配合伪代码理解一下:

代码语言:javascript
复制
// 全局执行上下文
GlobalExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 标识符绑定在这里 
    }
    outer: <null>,
    this: <global object>
  }
}

// 函数执行上下文
FunctionExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // 标识符绑定在这里 
    }
    outer: <Global or outer function environment reference>,
    this: <depends on how function is called>
  }
}

3.1.2 变量环境

变量环境(Variable environment)同样也是词法环境,因此它具有上面定义的词法环境的所有特征。这两者的区别主要在于: 在 ES6 中,词法环境用于存储函数声明和变量(let和const)绑定,而变量环境仅用于存储变量(var)绑定。

3.2 执行阶段

在执行阶段,完成对所有变量的分配,最后执行代码。

3.3 举例说明

通过一个例子来了解执行上下文的整个创建和执行过程。 以下面的代码为例

代码语言:javascript
复制
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
 var g = 20;
 return e * f * g;
}
c = multiply(20, 30);

在开始读取代码后,JavaScript引擎创建全局执行上下文并压栈,全局执行上下文的创建阶段的伪代码如下:

代码语言:javascript
复制
GlobalExectionContext = {
  // 词法环境
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 标识符绑定在这里
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: < func >
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },

  // 变量环境
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 标识符绑定在这里
      c: undefined,
    }
    outer: <null>,
    ThisBinding: <Global Object>
  }
}

之后进入全局执行上下文的执行阶段,开始进行变量分配/赋值,伪代码如下:

代码语言:javascript
复制
GlobalExectionContext = {
    // 词法环境
    LexicalEnvironment: {
        EnvironmentRecord: {
        Type: "Object",
        // 标识符绑定在这里
        a: 20,
        b: 30,
        multiply: < func >
        }
        outer: <null>,
        ThisBinding: <Global Object>
    },

    // 变量环境
    VariableEnvironment: {
        EnvironmentRecord: {
        Type: "Object",
        // 标识符绑定在这里
        c: undefined,
        }
        outer: <null>,
        ThisBinding: <Global Object>
    }
}

随着执行阶段的进行,我们遇到了multiply(20, 30),这是一个函数调用语句,所以此时创建了该函数对应的函数执行上下文并压栈,函数执行上下文的创建阶段的伪代码如下:

代码语言:javascript
复制
FunctionExectionContext = {
    // 词法环境
    LexicalEnvironment: {
        EnvironmentRecord: {
        Type: "Declarative",
        // 标识符绑定在这里
        Arguments: {0: 20, 1: 30, length: 2},
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>,
    },

    // 变量环境
    VariableEnvironment: {
        EnvironmentRecord: {
        Type: "Declarative",
        // 标识符绑定在这里
        g: undefined
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>
    }
}

之后进入函数执行上下文的执行阶段,开始进行函数内的变量的分配/赋值,伪代码如下:

代码语言:javascript
复制
FunctionExectionContext = {
    // 词法环境
    LexicalEnvironment: {
        EnvironmentRecord: {
        Type: "Declarative",
        // 标识符绑定在这里
        Arguments: {0: 20, 1: 30, length: 2},
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>,
    },

    // 变量环境
    VariableEnvironment: {
        EnvironmentRecord: {
        Type: "Declarative",
        // 标识符绑定在这里
        g: 20
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>
    }
}

函数执行完毕,函数执行上下文出栈,此时的执行上下文是全局执行上下文。由于函数的返回值被赋给变量c,此时全局执行上下文对应的全局词法环境得到更新,伪代码如下:

代码语言:javascript
复制
GlobalExectionContext = {
    // 词法环境
    LexicalEnvironment: {
        EnvironmentRecord: {
        Type: "Object",
        // 标识符绑定在这里
        a: 20,
        b: 30,
        multiply: < func >
        }
        outer: <null>,
        ThisBinding: <Global Object>
    },

    // 变量环境
    VariableEnvironment: {
        EnvironmentRecord: {
        Type: "Object",
        // 标识符绑定在这里
        c: 12000,
        }
        outer: <null>,
        ThisBinding: <Global Object>
    }
}

全局执行上下文的执行阶段结束,程序结束。

补充

在全局执行上下文创建阶段的伪代码中我们可以看到,letconst定义的变量没有任何与之关联的值,但var定义的变量设置为undefined

这是因为在创建阶段,JavaScript引擎会扫描一遍代码并解析所有的变量和函数声明,其中函数声明被存储在环境记录中,而变量的情况则比较特殊:var声明的变量将被设置为undefinedletconst声明的变量将保持未初始化。

因此,我们可以在声明之前就访问var定义的变量(尽管是undefined ),但如果在声明之前访问letconst定义的变量则会提示引用错误(因为在执行阶段之前其始终是未初始化的)。

这就是我们所谓的变量提升。

注: 在执行阶段,如果Javascript引擎在源代码中声明的实际位置找不到 let变量的值,那么将为其分配undefined值。


注意: 如果你发现译文和原文的说法存在出入,例如: 在原文中:

The execution context is created during the creation phase. Following things happen during the creation phase: 1.LexicalEnvironment component is created. 2.VariableEnvironment component is created.

Each Lexical Environment has three components: 1.Environment Record 2.Reference to the outer environment, 3.This binding

在译文中:

在任何 JavaScript 代码执行之前,执行环境经历了创建阶段,创建阶段包含以下三个事: 1.this 的值确定,也被称为 This Binding. 2.Lexical Environment 被创建。 3.Variable Environment 被创建。

在词法环境中,有两种组件: (1) environment record (2) reference to the outer environment.

这是因为(请看这幅图):

总而言之是由于ECAMAScript的标准变更导致的。原文最初是基于ES5编写的,this绑定的确是执行上下文创建阶段的一环,但是在ES2015 ES2018 的规范中,this绑定被并入词法环境的环境记录,所以原作者后来进行了更改,只是各种翻译和转载没有改过来就是了。关于具体内容,可以参考: ES5规范 ES6规范 上图的文章


本文参考: 原文 译文

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.执行上下文
    • 1.1 定义
      • 1.2 类型
      • 2.执行栈
      • 3.执行上下文的创建
        • 3.1 创建阶段
          • 3.1.1 词法环境
          • 3.1.2 变量环境
        • 3.2 执行阶段
          • 3.3 举例说明
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档