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
/let
和 function
声明),并且函数中声明的变量集永远不会改变。
eval
通过引入改变局部绑定的能力(通过在 运行 时间将新变量引入函数的范围)违反了这个假设。 with
通过在其属性在 运行 时间计算的函数中引入新的非词法绑定来违反此假设。静态代码分析无法始终确定 with
对象的属性,因此分析器无法确定 with
块中存在哪些变量。重要的是,提供给 with
的对象可能会在函数的执行之间发生变化,这意味着永远无法保证函数的该词法部分中的变量集是一致的。
考虑简单的函数:
function foo() {
var a, b;
function c() { ... }
...
}
foo
中的所有点都有三个局部范围变量,a
、b
和 c
。优化器可以将永久类型的 "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
中有 a
、b
或 c
,我们要到 运行 时才能知道它是否引用了 [=] 的变量33=] 或由 with(egg)
词法作用域创建的。
为了展示这个问题的半实际例子,最后考虑:
function baz(egg) {
with(egg) {
return function() { return whereami; }
}
}
当内部函数执行时(例如bar({...})()
),执行引擎将查找作用域链以找到whereami
。如果允许优化器将永久范围注释附加到 baz
,那么执行引擎将立即知道在函数的 baz
闭包中查找 whereami
的值,因为那样会保证是 whereami
的家(范围链上的任何类似命名的变量都会被最近的变量覆盖)。然而,它并不知道whereami
是否存在于baz
中,因为它可以由egg
的内容有条件地创建创建该内部函数的特定 运行 of bar
。所以,不得不检查,没有使用优化。
阅读 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
/let
和 function
声明),并且函数中声明的变量集永远不会改变。
eval
通过引入改变局部绑定的能力(通过在 运行 时间将新变量引入函数的范围)违反了这个假设。 with
通过在其属性在 运行 时间计算的函数中引入新的非词法绑定来违反此假设。静态代码分析无法始终确定 with
对象的属性,因此分析器无法确定 with
块中存在哪些变量。重要的是,提供给 with
的对象可能会在函数的执行之间发生变化,这意味着永远无法保证函数的该词法部分中的变量集是一致的。
考虑简单的函数:
function foo() {
var a, b;
function c() { ... }
...
}
foo
中的所有点都有三个局部范围变量,a
、b
和 c
。优化器可以将永久类型的 "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
中有 a
、b
或 c
,我们要到 运行 时才能知道它是否引用了 [=] 的变量33=] 或由 with(egg)
词法作用域创建的。
为了展示这个问题的半实际例子,最后考虑:
function baz(egg) {
with(egg) {
return function() { return whereami; }
}
}
当内部函数执行时(例如bar({...})()
),执行引擎将查找作用域链以找到whereami
。如果允许优化器将永久范围注释附加到 baz
,那么执行引擎将立即知道在函数的 baz
闭包中查找 whereami
的值,因为那样会保证是 whereami
的家(范围链上的任何类似命名的变量都会被最近的变量覆盖)。然而,它并不知道whereami
是否存在于baz
中,因为它可以由egg
的内容有条件地创建创建该内部函数的特定 运行 of bar
。所以,不得不检查,没有使用优化。