词法作用域
在上一篇中,我们将“作用域”比作一套规则,引擎根据这套规则在当前作用域及嵌套的作用域中查询标识符(变量)。
作用域的工作模式主要有两种:词法作用域和动态作用域。 JavaScript使用的是词法作用域。
简单的说,词法作用域就是定义在词法阶段(编译器的一个工作阶段叫词法化,会对源代码的字符进行检查,如,var a = 2会被分解成词法单元var、a、=、2)的作用域。
换句话说,词法作用域是由你在编写代码时将变量和块作用域写在哪里来决定的,因此词法分析器处理代码时会保持作用域不变(大部分情况是这样的)。
以下代码中有3个逐级嵌套的作用域。
js
function foo(a) {
let b = a * 2
function bar(c) {
console.log(a, b, c)
}
bar(b * 3)
}
foo(2) // 2, 4, 12
- 全局作用域,其中只有一个标识符:foo。
- foo所创建的作用域,其中有只有三个标识符:a、b、bar。
- bar所创建的作用域,其中只有一个标识符:c。
作用域由其代码块写在哪里决定。
考虑如下代码
js
function foo() {
console.log(a)
}
function bar() {
let a = 2
foo()
}
let a = 1
bar() // 1
foo是定义在全局的函数,因此引擎在执行console.log(a)
会先在foo作用域内对a进行RHS查询,查询不到就会在foo的外层嵌套作用域(也就是全局作用域)进行RHS查询。
无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只有函数被声明时所处的位置决定。
查找
作用域查找始终从所处的最内部作用域开始,逐渐往外或者说向上进行,直到匹配到第一个标识符为止。
词法作用域只会查找一级标识符,如foo.bar.baz
词法作用域查找只会试图查找foo标识符,找到这个变量后,对象属性访问规则会分别接管对bar和baz属性的访问。
欺骗词法
eval和with可以在运行时修改词法作用域。
JavaScript引擎会在编译阶段进行数项性能优化,其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数定义的位置,才能在执行过程中快速找到这些标识符。
而eval和with会导致引擎无法在编译阶段对其作用域查找进行优化,从而导致性能下降。