JavaScript 专家:为什么 `with` 会使编译器的范围相关优化无效

JavaScript experts: why does `with` nullify the compiler's scope-related optimizations

阅读 Kyle Simpson 的《你不知道的 JS:范围和闭包》,他认为你应该远离 eval() 函数和 with 关键字,因为每当编译器看到这些 2 (我在解释),它不执行一些与词法范围相关的优化和存储标识符的位置,因为这些关键字可能会修改词法范围,从而使编译器的优化有点不正确(我假设优化是类似于编译器存储每个标识符的位置,因此它可以提供标识符的值而无需在运行时请求时搜索它)。

现在我明白了为什么当你使用 eval() 关键字时会发生这种情况:你的 eval 可能正在评估用户输入,而该用户输入可能是一个新变量的声明,它隐藏了你稍后访问假设正在执行的函数,如果编译器存储了静态位置,则访问将 return 错误标识符的值(因为访问应该 returned 的值eval() 声明的标识符,但它 return 编辑了编译器存储的变量值以优化查找)。所以我只是假设这就是编译器在您的代码中发现 eval() 时不执行其范围相关查找的原因。

但是为什么编译器对 with 关键字做同样的事情呢?这本书说它这样做是因为 with 在运行时创建了一个新的词法范围,并且它使用作为参数传递给 with 的对象的属性来声明一些新的标识符。我真的不知道这意味着什么,我很难想象这一切,因为这本书中所有与编译器相关的东西都是理论。

我知道我可能走错了路,那样的话,请纠正我所有的误解:)

举个例子:

{
 let a = 1; //stored at 123
 {
   let b = 2; //stored at 124
   console.log(a/*123*/,b/*124*/);
 }
}

现在这个:

{
 let a = 1;//stored at 123
 with({a:3}){
   console.log(a /*123 ??*/);
 }
}

这里提到的优化是基于这样一个事实:函数内声明的变量总是可以通过简单的代码静态分析来确定(即通过查看 var/letfunction 声明),并且函数中声明的变量集永远不会改变。

eval 通过引入改变局部绑定的能力(通过在 运行 时间将新变量引入函数的范围)违反了这个假设。 with 通过在其属性在 运行 时间计算的函数中引入新的非词法绑定来违反此假设。静态代码分析无法始终确定 with 对象的属性,因此分析器无法确定 with 块中存在哪些变量。重要的是,提供给 with 的对象可能会在函数的执行之间发生变化,这意味着永远无法保证函数的该词法部分中的变量集是一致的。

考虑简单的函数:

function foo() {
    var a, b;
    function c() { ... }
    ...
}

foo 中的所有点都有三个局部范围变量,abc。优化器可以将永久类型的 "note" 附加到表示 "This function has three variables: a, b, and c. This will never change."

的函数

现在考虑:

function bar(egg) {
    var a, b;
    function c() { ... }

    with(egg) {
        ...
    }
}

with块中,不知道哪些变量存在或不存在。如果 with 中有 abc,我们要到 运行 时才能知道它是否引用了 [=] 的变量33=] 或由 with(egg) 词法作用域创建的。

为了展示这个问题的半实际例子,最后考虑:

function baz(egg) {
    with(egg) {
        return function() { return whereami; }
    }
}

当内部函数执行时(例如bar({...})()),执行引擎将查找作用域链以找到whereami。如果允许优化器将永久范围注释附加到 baz,那么执行引擎将立即知道在函数的 baz 闭包中查找 whereami 的值,因为那样会保证是 whereami 的家(范围链上的任何类似命名的变量都会被最近的变量覆盖)。然而,它不知道whereami是否存在于baz中,因为它可以由egg的内容有条件地创建创建该内部函数的特定 运行 of bar。所以,不得不检查,没有使用优化。