【问题标题】:declaring a variable twice in IIFE在 IIFE 中两次声明一个变量
【发布时间】:2019-08-28 19:29:06
【问题描述】:

我通过互联网上的这个有趣的测验。

console.log((function(x, f = (() => x)){
  var x;
  var y = x;
  x = 2;
  return [x, y, f()]
})(1))

选择如下:

  1. [2,1,1]

  2. [2,未定义,1]

  3. [2, 1, 2]

  4. [2, 未定义, 2]

我选择了解决方案 2 TBH,基于 x 已被重新定义,y 被声明并定义为没有值,并且 f 具有不同的范围,因此获得全局 x 内存点而不是函数 x 内存点。

不过,我在jsbin.com尝试过

我发现它是解决方案 1,虽然我不确定为什么会发生这种情况,但我弄乱了函数体并从函数体中删除了 var x,我发现响应更改为 #3,这对于 x值发生了变化,因此它显示 x 和 f 为 2 和 y 为 1,这是全局声明的。

但我仍然不明白为什么它显示 1 而不是 undefined。

【问题讨论】:

  • 我发现解决这些问题的最佳方法是使用调试器逐行遍历它们,和/或在每行之后打印出值。
  • var x; 没有在函数范围内定义新变量,而 var x = somevalue;
  • @alex 确实如此。 ...
  • @JonasWilms 我明白了,本来期望 x 是未定义的,隐式复制函数形式值对我来说真的很奇怪。
  • 很高兴有人问这个问题:)

标签: javascript iife


【解决方案1】:

但我仍然不明白为什么它显示 1 而不是 undefined。

不只是你。这是规范的一个深刻而黑暗的部分。 :-)

这里的关键是有两个xs。对真的。有参数x,还有变量x

包含表达式 的参数列表(如f 的默认值)有其自己的范围 与函数体的范围分开。但在参数列表可能包含表达式之前,在带有 x 参数的函数中使用 var x 没有效果(x 仍然是参数,具有参数的值)。因此,为了保留这一点,当其中有一个带有表达式的参数列表时,会创建一个单独的变量,并将参数的值复制到函数体开头的变量中。这就是看似奇怪 (不,不仅仅是表面上)奇怪行为的原因。 (如果你是那种喜欢深入研究规范的人,这个复制是FunctionDeclarationInstantiation 的第 28 步。)

由于f的默认值() => x是在参数列表范围内创建的,它指的是参数x,而不是var。

所以第一个解决方案[2, 1, 1]是正确的,因为:

  • 2 被分配给函数体中的 var x。所以在函数的最后,var x2
  • 1x 得到值 2 之前从 var x 分配给 y,所以在函数结束时,y1
  • 参数x 的值从未改变,所以f() 在函数末尾产生1

好像代码是这样写的(我删除了不必要的括号并添加了缺少的分号):

console.log(function(param_x, f = () => param_x) {
  var var_x = param_x;
  var y = var_x;
  var_x = 2;
  return [var_x, y, f()];
}(1));

...我从函数体中去掉了var x,我发现响应变成了#3...

#3 是[2, 1, 2]。这是正确的,因为当您从函数中删除var x 时,只有一个x,参数(由参数列表中的函数体继承)。因此,将2 分配给x 会更改f 返回的参数值。

param_xvar_x 为例,如果您从中删除var x;,它会变成这样:

console.log(function(param_x, f = () => param_x) {
  var y = param_x;
  param_x = 2;
  return [param_x, y, f()];
}(1));

这是原始代码的注释说明(删除了多余的括号并添加了缺少的分号):

//                   /---- the parameter "x"
//                   v  vvvvvvvvvvv--- the parameter "f" with a default value
console.log(function(x, f = () => x) {
  var x;      // <=== the *variable* x, which gets its initial value from the
              //      parameter x
  var y = x;  // <=== sets y to 1 (x's current value)
  x = 2;      // <=== changes the *variable* x's value to 2
  //      +---------- 2, because this is the *variable* x
  //      |  +------- 1, because this is the variable y
  //      |  |   +--- 1, because f is () => x, but that x is the *parameter* x,
  //      |  |   |       whose value is still 1
  //      v  v  vvv
  return [x, y, f()];
}(1));

关于您的标题的最后说明:

在 IIFE 中两次声明一个变量

变量只声明一次。另一件事是参数,而不是变量。区别很少重要......这是罕见的时刻之一。 :-)

【讨论】:

  • 这现在在我的“关于 JavaScript 的新奇事”列表中,以及 class 声明中的(还不是真正标准的)实例属性初始化表达式的语义。
  • @Pointy - 哦,这比字段声明更奇怪。 :-D
  • 好吧,字段声明对我来说很刺耳,因为 this 是实例,而不是像对象初始化程序那样周围范围的 this。如果您认为 class 声明类似于宏扩展,我想这并不是真的“奇怪”。
  • @Pointy - 是的。我认为它们是重新定位到构造函数开头的代码(如果它是子类,则在 super 调用之后)。这就是 Java 对实例字段初始化器和实例初始化块所做的事情(它们字面上复制到 Java 中的 each 构造函数的开头;幸好在 JavaScript 中我们只有一个)。 JavaScript 有效地采用了相同的方法。
  • @T.J.Crowder 这是一个很好的解释,虽然没有明显的分配,但 var x 直接从参数 x 分配真的很奇怪,感谢您清除这个。
【解决方案2】:

该代码的棘手部分是 =&gt; 函数是作为默认参数值表达式的一部分创建的。在参数默认值表达式中,范围包括左侧声明的参数,在这种情况下包括参数x。因此,由于这个原因,=&gt; 函数中的x 实际上是第一个参数。

该函数仅使用一个参数1 调用,因此当=&gt; 函数被调用时,它返回的是[2, 1, 1]

var x 声明,正如 Crowder 先生指出的那样,具有(至少对我来说有点奇怪)在函数范围内创建一个 new x 的效果,并将其复制到其中参数 x 的值。没有它,就只有一个(参数)。

【讨论】:

  • @JonasWilms 你是对的; var 声明必须...做 something 但对我来说这似乎并不明显。就好像var 声明在函数范围内创建了一个新的x,但它显然获得了参数 x 的值(这是我所期望的),留下参数显然在它自己的范围内。
  • 我猜这些值是从默认初始化范围复制到主体范围的。我已经深入研究规范
  • 有道理。如规范中所述:NOTE: A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body. 这是合理的。
  • @Pointy - 这是规范中非常深刻、黑暗的部分。 :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-29
  • 2022-06-14
  • 2014-01-31
  • 1970-01-01
  • 2010-10-30
相关资源
最近更新 更多