词法作用域-你不知道的JS
编译
在传统编译语言的流程中,程序中的一段源代码在执行之前会经历三个步骤,统称为“编译”。
编译可以分为三个阶段
- 词法分析。例如,
var a = 2;
这段代码通常会被分解成为下面这些词法单元:var
、a
、=
、2
、;
- 语法分析。这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)在线生成AST
- 代码生成。将AST 转换为可执行代码的过程被称为代码生成。简单来说就是有某种方法可以将
var a = 2;
的AST 转化为一组机器指令,用来创建一个叫作a 的变量(包括分配内存等),并将一个值储存在a 中。
词法阶段
词法作用域
词法作用域就是定义在词法阶段的作用域。
换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样的)。
function foo(a) {
var b = a * 2;
function bar(c) {
console.log( a, b, c );
}
bar( b * 3 );
}
foo( 2 ); // 2, 4, 12
代码中有三个作用域。
- 全局作用域,只有一个foo标识符
- foo创建的作用域,三个标识符,a b c
- bar创建的作用域,一个标识符,c
这三个作用域由其对应的作用域块代码写在哪里决定,它们是逐级包含的。而且是严格包含,没有哪个作用域同时出现在两个作用域里。
查找范围
作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的标识符,这叫作“遮蔽效应”(内部的标识符“遮蔽”了外部的标识符)。抛开遮蔽效应,作用域查找始终从运行时所处的最内部作用域开始,逐级向外或者说向上进行,直到遇见第一个匹配的标识符为止。
无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。
词法作用域查找只会查找一级标识符,比如a、b 和c。如果代码中引用了foo.bar.baz
这种,词法作用域查找只会试图查找foo
标识符,找到这个变量后,对象属性访问规则会分别接管对bar 和baz 属性的访问。