从ES6规范解读执行上下文

1,319 阅读4分钟

本文根据ES6 规范关于执行上下文的内容来解读 JavaScript 程序运行的实际情况。

Lexical Environments(词法环境)

在论述执行上下文之前,我们需要了解一个概念:词法环境。

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code.

简单来说,词法环境就是用来处理代码块的变量(标识)关系。

词法环境由两部分组成:Environment Record(环境记录)和 outer Lexical Environment(外部词法环境)。

Environment Record(环境记录)

环境记录就是储存变量(标识)的地方。

环境记录有两种:declarative Environment Records(声明式环境记录)和 object Environment Records(对象式环境记录)。

declarative Environment Records(声明式环境记录)

直接绑定变量(标识)到ECMAScript language values(基本类型对应的值),函数声明、变量声明等都属于声明式环境记录。

object Environment Records(对象式环境记录)

绑定变量(标识)到某些对象的属性上。

例如 with 语句,把变量(标识)绑定到了对象的属性上。

outer Lexical Environment(外部词法环境)

外部词法环境指向继承的 Lexical Environments(词法环境)。

Call Stack(调用栈)

JavaScript 执行代码是一块一块执行,执行代码块之前就会创建相应的执行上下文。

ES5 规范把代码分为三类代码块:

  • 全局代码块(Global code)
  • 函数代码块(Function code)
  • eval 代码块(Eval code)

提几个要点:

  1. 栈是后进先出的数据结构。
  2. 代码运行永远是从全局代码块开始,所以全局上下文会一直在栈底。
  3. 执行代码块之前,相应的执行上下文入栈。
  4. 代码块执行完毕,相应的执行上下文出栈。

这里引用David Shariff 的例子来展示代码块的执行:

(function foo(i) {
  if (i === 3) {
    return;
  } else {
    foo(++i);
  }
})(0);

Execution Context(执行上下文)

在 ES6 规范里描述了执行上下文的 5 个组成部分,这里只讨论影响作用域的两个部分:LexicalEnvironment(词法环境)和 VariableEnvironment(变量环境)。

注意:这里的 LexicalEnvironment 和上文的 Lexical Environments 不是一个概念。

简单理解:LexicalEnvironment 和 VariableEnvironment 都是 Lexical Environments 这个“类”衍生出的实例。LexicalEnvironment 反映了所有变量(标识)的引用关系,VariableEnvironment 只包含变量声明(var)。

程序分析

我们先来分析一个简单的例子:

var a = 0;
let b = 0;
var c = 0;

function f1() {
  var a = 1;
  let b = 1;
  console.log(`f1: ${a} ${b} ${c}`); // 1 1 0
}

function f2() {
  f1();
  var c = 1;
  console.log(`f2: ${a} ${b} ${c}`); // 0 0 1
}

f2();

首先,创建全局执行上下文:

GlobalExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      a: undefined,
      b: <no-initialize>,
      c: undefined,
      f1: <function>,
      f2: <function>,
    },
    outer: null,
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      a: undefined,
      c: undefined,
    },
    outer: null,
  },
};

调用 f2,f2 上下文入栈:

f2ExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Arguments: { length: 0 },
      c: undefined,
    },
    outer: GlobalExecutionContext.LexicalEnvironment,
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      c: undefined,
    },
    outer: GlobalExecutionContext.LexicalEnvironment,
  },
};

f2 函数中调用了 f1,f1 上下文入栈:

f1ExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Arguments: { length: 0 },
      a: undefined,
      b: <no-initialize>,
    },
    outer: GlobalExecutionContext.LexicalEnvironment,
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      a: undefined,
    },
    outer: GlobalExecutionContext.LexicalEnvironment,
  },
};

执行 f1:

f1ExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Arguments: { length: 0 },
      a: 1,
      b: 1,
    },
    outer: GlobalExecutionContext.LexicalEnvironment,
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      a: 1,
    },
    outer: GlobalExecutionContext.LexicalEnvironment,
  },
};

通过 GetBindingValue 查询 a,b,c 值, 未查询到 c,往 outer(即 GlobalExecutionContext.LexicalEnvironment)中查询到 c。

打印 f1: 1 1 0 ,f1 执行完毕,出栈。

继续执行 f2:

f2ExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Arguments: { length: 0 },
      c: 1,
    },
    outer: GlobalExecutionContext.LexicalEnvironment,
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      c: 1,
    },
    outer: GlobalExecutionContext.LexicalEnvironment,
  },
};

打印 f2: 0 0 1 ,f2 执行完毕,出栈。

代码执行结束,全局上下文出栈。

注意:在词法作用域和动态作用域中提到,JavaScript 是词法作用域,所以外部词法环境根据代码块的文本位置确定。

LexicalEnvironment(词法环境)变化

来看一个 LexicalEnvironment(词法环境)变化的例子:

const a = 0;
if (true) {
  const a = 1;
}

创建全局执行上下文:

GlobalExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      a: <no-initialize>,
    },
    outer: null,
  },
  VariableEnvironment: {
    EnvironmentRecord: {},
    outer: null,
  },
};

Usually a Lexical Environment is associated with some specific syntactic structure of ECMAScript code such as a FunctionDeclaration, a BlockStatement, or a Catch clause of a TryStatement and a new Lexical Environment is created each time such code is evaluated.

所以执行到 if 语句的时候,创建了一个新的 LexicalEnvironment,并且外部词法环境指向当前的 LexicalEnvironment 拷贝,所以此时全局执行上下文:

GlobalExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      a: <no-initialize>,
    },
    outer: LexicalEnvironmentCopy,
  },
  VariableEnvironment: {
    EnvironmentRecord: {},
    outer: LexicalEnvironmentCopy,
  },
};

知识点

在创建执行上下文的时候:

  • var 声明的变量会被初始化为 undefined。
  • 函数声明随情况不同会被初始化为 undefined 或者函数对象
// 情况1
console.log(func);
function func() {}

// 情况2
console.log(func);
if (0) function func() {}

结语

因为上下文无法观察,所以只能通过参考规范推测总结。

最后欢迎大家提出自己的修改建议或者分享心得