第二章 词法作用域

Thursday, August 19, 2021

词法作用域就是定义在词法阶段的作用域,由你在写 代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样的)。

词法阶段

作用域由其对应的作用域块代码写在哪里决定,它们是逐级包含的。

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,那么运行起来一定会变得非常慢。无论引擎多聪 明,试图将这些悲观情况的副作用限制在最小范围内。