【问题标题】:Why does block assigned value change global variable? [duplicate]为什么块赋值会改变全局变量? [复制]
【发布时间】:2023-03-09 00:07:01
【问题描述】:

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

  function a() {}
  a = 21
  console.log(a)
}
console.log(a)

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

用 chrome 调试,结果是这样的

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

【问题讨论】:

  • 我怀疑这是因为Web Compatibility Semantics 在非严格模式下生效。见also
  • @52d6c6af 是的。但是,我在您链接的答案中描述的行为不符合此处观察到的行为。因此,如果同名的上层变量已经存在,则要么与特殊大小写的 web 兼容语义有关,要么是 Chrome 中的错误。
  • Firefox 做同样的事情。 Safari 没有(第一个 console.log 打印 0,最后一个 console.log 打印 21)。但我碰巧知道 Safari 中的一个错误与块中的函数声明有关。这表明这种行为可能是故意的。也许吧。

标签: javascript


【解决方案1】:

观察到的行为是非严格模式特有的,Firefox 也是如此。

这样做的原因是它遵循 Web 兼容性语义,如规范中的 Annex B 3.3 中所述。

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

当一个内部函数a存在于一个块中时,在草率模式下,并且当 Web 兼容性语义适用(在 规范),然后:

  1. 内部函数a在块内被提升为let-like块范围(“(let) a”)
  2. 同时,在包含 块(“(var) a”)
  3. 到达声明内部函数的行时,(let) a 的当前值被复制到 (var) a (!!)
  4. 从块内部对名称a 的后续引用将引用(let) a

因此,对于以下内容:

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

...这就是发生的事情:

第一行:(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 在 Twitter 上said

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

...and:

应该是function a(){}!因为在块内,所有对a 的显式引用都指向块级绑定。只有隐式 B.3.3 分配应该转到外部 a

最后说明:Safari 会给出完全不同的结果(0 由第一个 console.log 打印,21 由最后一个 console.log 打印)。据我了解,这是因为 Safari 还没有对 Web 兼容性语义 (example) 进行编码。

故事的寓意?使用严格模式。

Moredetailsherehere

【讨论】:

  • 啊,完美,这种行为仍然与另一个线程中的my explanation 匹配。我从来没有想到(显然,规范编辑也没有想到)块作用域的变量不会保存函数值,而是当它被复制到外部作用域变量时。
  • 我认为某处有电线交叉。我认为期望函数对象将在内部函数的声明点分配给外部var。实现的是在那个点复制与标识符关联的值,这是微妙的不同。
  • @BenAston,那么为什么“(let) a 的值被复制到(var) a”发生在内部函数的声明中,为什么不在 (let) a = function() {} 上面 (let) a = 1?
【解决方案2】:

这也很“有趣”。看来a = 21a赋值给全局var,函数引用不可达。

var a = 0;
if (true) {
  function a() {}
  console.log(typeof a, a) // number 0
  a = 21;
}
console.log(typeof a, a) // number 21

【讨论】:

    【解决方案3】:

    a = 1; 重新分配给原始变量,使其值为1function a() {} 屏蔽 a 和“接管”a 仅在 if 条件下。阅读有关shadowing and masking 的更多信息。

    【讨论】:

    • 这是正确的。条件代码块中的function 声明语句也是可憎的。
    • 这不是一个充分的解释,因为这不是正常行为。应该提升块中的功能,这意味着a = 1 会影响内部a。在严格模式下,行为符合预期。就好像遮蔽只在函数声明之后才生效。
    • @Pointy 考虑在严格模式下,它如何按预期记录210。还有其他事情发生。
    • @Bergi 同意我现在查看它,并且我同意它必须与兼容性有关。在这样一个 function 声明可能被提升到封闭函数或全局范围的世界中,if 之外的 var 声明将隐藏函数。也许这是某种妥协的结果。
    • 而且我仍然认为function 声明不应该在条件块中:)
    【解决方案4】:

    你好。 让我们这样看,当你声明一个名为 'a=1' 的变量(你声明的变量是一个全局变量)时,一个内存房屋被分配给一个变量 具有此值:1
    在你声明函数之后: function a() {} 'a' 的变量类型不再存在记忆库,并且 a 是一个函数。 但是,'a' 的内存空间并没有清除,它仍然可用,就像调用一次并返回最后一个赋值的函数一样! 所以,当你写这段代码时:

      a = 1;
      function a() {}
    

    就像:

    function a(){
    return 1;
    }
    console.log(a());
    

    这个问题就像是因为一个不完整的性质(我的意思是记忆屋类型)改变。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-25
      • 1970-01-01
      • 2018-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多