为什么块赋值会改变全局变量?

Zen*_*art 17 javascript

var a = 0;
if (true) {
  console.log(a)
  a = 1;

  function a() {}
  a = 21
  console.log(a)
}
console.log(a)
Run Code Online (Sandbox Code Playgroud)

在我看来,因为函数声明提升,a = 1以及 a = 21会改变局部函数变量,所以在块中会输出21,外面是0,但真正的结果是在输出1之外。

用chrome调试,结果是这样

当运行时function a() {},它会改变局部和全局变量。太奇怪了?谁能给我解释一下?

Ben*_*Ben 13

观察到的行为是非严格模式特有的,Firefox 也做同样的事情。

之所以会这样,是因为它遵循了 Web 兼容性语义,如附件 B 3.3 中所述规范中所述。

细节非常复杂,但这里是引擎这部分的作者实现的:

a块中存在内部函数时,处于草率模式,并且应用 Web 兼容性语义时(规范中描述的场景),则:

  1. 内部函数alet块内用-like 块作用域提升("(let) a ")
  2. 同时,在包含块 (" ") 的作用域中创建了一个变量,同样具有 name a,但具有var语义(即函数作用域(var) a
  3. 当到达声明内部函数的行时,将的当前值(let) a复制到(var) a(!!)
  4. a块内部对 name 的后续引用将引用(let) a

因此,对于以下情况:

1:  var a = 0
2:  if(true) {
3:    a = 1
4:    function a() {}
5:    a = 2
6:  }
7:  console.log(a)
Run Code Online (Sandbox Code Playgroud)

...这是发生的事情:

第 1 行: (var) a添加外部作用域的变量环境,并0赋值给它

第 2 行: 创建一个 if 块,(let) a(即function a() {})被提升到块的顶部,阴影(var) a

第 3 行: 1分配给(let) a(注意,不是(var) a

第 4 行: 的值(let) a被复制到 中(var) a,因此(var) a变为1

第 5 行: 2分配给(let) a(注意,不是(var) a

第 7 行: (var) a打印到控制台(所以1打印)

Web 兼容性语义是一组行为,这些行为尝试对回退语义进行编码,以使现代浏览器能够尽可能多地保持与 Web 上遗留代码的向后兼容性。这意味着它对规范之外实现的行为进行编码,并且由不同的供应商独立实现。在非严格模式下,由于浏览器供应商“走自己的路”的历史,几乎可以预料到奇怪的行为。

但是请注意,规范中定义的行为可能是错误传达的结果。Allen Wirfs-Brock在推特上

无论如何,根据我想象的任何合理解释,最后报告的...结果 a==1 都不正确。

...

应该是function a(){}!因为在块内,所有a对块级绑定的显式引用。只有隐含的 B.3.3 赋值应该去外部a

最后请注意:Safari浏览器,给出了一个完全不同的结果(0由第一印刷console.log,并且21通过最终印刷console.log)。据我了解,这是因为 Safari 还没有对 Web 兼容性语义进行编码(例如)。

故事的道德启示?使用严格模式。

更多细节在这里这里

  • 啊,完美,这种行为仍然符合另一个线程中的[我的解释](/sf/answers/2202313081/)。我从来没有想到(显然,规范编辑者也没有想到)块作用域变量在复制到外部作用域变量时不会保存函数值,而是保存其他内容。 (2认同)