词法作用域就是定义在词法阶段的作用域,由你在写 代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样的)。
词法阶段
作用域由其对应的作用域块代码写在哪里决定,它们是逐级包含的。
function foo(a) {
var b = a * 2
function bar(c) {
console.log(a, b, c)
}
bar(b * 3)
}
foo(2) // 2, 4, 12
遮蔽效应
作用域查找会在找到第一个匹配的标识符时停止,在多层的嵌套作用域中可以定义同名的标识符,这叫作“遮蔽效应”(内部的标识符“遮蔽”了外部的标识符)。
全局变量可通过window.a
间接访问,非全局的变量 如果被遮蔽了,无论如何都无法被访问到
var a = 1
function foo() {
var a = 2
console.log(a)
}
foo() // 2
console.log(a) // 1
var a = 1
function foo() {
console.log(a)
}
function bar() {
var a = 2
foo()
}
bar() // 1
词法作用域只关注函数在何处被声明, 而不是在何处被调用,foo
函数位于全局作用域被声明, 所以它对 a
变量的 RHS 查找是在 全局作用域 中。
欺骗词法
理解欺骗词法很简单, 即我们定义作用域时, 并不是通过书写代码的阶段定义的, 而是在运行的阶段定义
function foo(str, a) {
eval(str)
console.log(a + b)
}
foo('var b = 2', 3) // 5
function foo(obj) {
with (obj) {
a = 2
}
}
var o1 = { a: 3 }
var o2 = { b: 3 }
foo(o1)
console.log(o1.a) // 2
foo(o2)
console.log(o2.a) // undefined
console.log(a) // 2——不好,a 被泄漏到全局作用域上了!
性能
JavaScript 引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的 词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。 发现eval(..) 或 with,引擎只能简单地假设关于标识符位置的判断都是无效的,因为无法在词法分析阶段明确知道 eval(..) 会接收到什么代码,也无法知道传递给 with 用来创建新词法作用域的对象的内容到底 是什么,如果代码中大量使用 eval(..) 或 with,那么运行起来一定会变得非常慢。无论引擎多聪 明,试图将这些悲观情况的副作用限制在最小范围内。