词法环境(Lexical Environments)

官方规范对词法环境的说明是:词法环境(Lexical Environments)是 一种规范类型,用于根据ECMAScript代码的词法嵌套结构来定义标识符与特定变量和函数的关联

通俗来说,词法环境就是一套约定好的规则。我们写代码,应该按照这个规则来。JS引擎对JS源码进行词法分析,也是按照这个规则。

词法环境,其实就是作用域。

得出结论:
一、在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理JS引擎如何在当前作用域以及嵌套的子作用域中根据标识符(变量或者函数)名称进行变量查找。
二、JavaScript中有全局作用域与函数作用域(因为eval我们平时开发中几乎不会用到它,这里不讨论)。

作用域链

作用域是一套规则;而作用域链,则是作用域的具体实现。

作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问

作用域链,在函数声明阶段确认。如果要结合 JavaScript 引擎来理解的话,作用域链,就是在代码解析阶段确认的。

为了帮助大家理解作用域链,我我们先结合一个例子,以及相应的图示来说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a = 20;

function test() {
var b = a + 10;

function innerTest() {
var c = 10;
return b + c;
}

return innerTest();
}

test();

在上面的例子中,全局,函数test,函数innerTest的执行上下文先后创建。我们设定他们的变量对象分别为VO(global),VO(test), VO(innerTest)。而innerTest的作用域链,则同时包含了这三个变量对象,所以innerTest的执行上下文可如下表示。

很多人会误解为当前作用域与上层作用域为包含关系,但其实并不是。以最前端为起点,最末端为终点的单方向通道我认为是更加贴切的形容。如图:

作用域链图示
作用域链图示

注意:因为变量对象在执行上下文进入执行阶段时,就变成了活动对象,这一点在上一篇文章中已经讲过,因此图中使用了AO来表示Active Object。

作用域链是由一系列变量对象组成,我们可以在这个单向通道中,查询变量对象中的标识符,这样,就能访问到上一层作用域中的变量。

最后,用一个问题,验证一下大家对作用域和作用域链以及变量对象的理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = 20;

function foo() {
if (!a) {
a = 100;
}

var a = 10;

return a;
}

console.log(foo());