【问题标题】:JavaScript experts: why does `with` nullify the compiler's scope-related optimizationsJavaScript 专家:为什么 `with` 会使编译器的作用域相关优化无效
【发布时间】:2017-09-11 17:37:48
【问题描述】:

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

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

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

我知道我可能走错了路,在这种情况下,请纠正我所有的误解:)

【问题讨论】:

  • @Bergi 我不是在问它为什么有害,而是在问“如果代码中存在with,为什么编译器不执行与范围相关的优化”。这篇文章回答了哪些问题?
  • 在 2017 年不使用 with 的原因不是为了优化,而是因为我们正在编写严格模式代码或 ES6,它甚至不合法。
  • @torazaburo 很好,作者认为尽管对with 提出了所有负面观点,但他们都忽略了最重要的一点:with 会损害性能。我只是在问为什么。

标签: javascript v8 spidermonkey lexical-scope


【解决方案1】:

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

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

考虑简单的函数:

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

foo 中的所有点都具有三个局部范围变量,abc。优化器可以为函数附加一种永久性的“注释”,上面写着“这个函数有三个变量:abc。这永远不会改变。”

现在考虑:

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

    with(egg) {
        ...
    }
}

with 块中,不知道哪些变量将存在或不存在。如果with 中有abc,直到运行时我们才知道它是指bar 的变量还是由with(egg) 词法范围创建的变量。

为了展示一个半实际的例子来说明这是一个问题,最后考虑一下:

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

当内部函数执行时(例如bar({...})()),执行引擎将查找作用域链以找到whereami。如果允许优化器将永久范围注释附加到baz,那么执行引擎将立即知道在函数的baz 闭包中查找whereami 的值,因为这将保证是home whereami (作用域链上任何类似命名的变量都将被最接近的变量所遮蔽)。但是,它知道whereami 是否存在于baz 中,因为它可以由egg 的内容在bar 的特定运行中创建内在功能。所以必须检查,优化不使用。

【讨论】:

  • 添加一些代码示例会增加答案的价值:)
  • "直到运行时我们才知道" - 我们甚至不总是知道 在运行时 何时输入 with 语句.每次访问变量时,我们都需要检查对象。当然,这也可以防止重新排序、复制或避免变量访问的编译器优化。
  • @Taurus 是的,对不起,那是错字;我打算将该函数命名为bar,但错误地将其保留为foo。我重命名了该示例中的内容并编辑了文本。
  • @Taurus 换一种说法:严格来说,这里匿名函数闭包的作用域父级不是baz,而是现在存在一个词法作用域对象(其变量由egg 定义)位于匿名函数和baz 之间。那个词法作用域对象不能有正常的“作用域链上存在哪些变量?”对其进行了优化。
  • @Taurus 我明白了——是的,我们需要先查阅不断变化的词法范围对象,看看它目前是否有一个 whereami 变量,然后才能使用 @ 987654364@ 存在于baz 中。 “词法范围对象”实际上是指 ECMAScript 规范中定义的“Lexical Environment”的内存表示,尽管在这种情况下,词法环境中的变量集定义的实际 JavaScript 对象(这里是egg),所以我的意思并没有太大的实际差异。
【解决方案2】:

举个例子:

{
 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 ??*/);
 }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-16
    • 2011-12-13
    • 1970-01-01
    • 2011-10-05
    • 2020-07-24
    • 1970-01-01
    • 2021-02-01
    • 2015-09-15
    相关资源
    最近更新 更多