在 IIFE 中声明一个变量两次

declaring a variable twice in IIFE

我在互联网上完成了这个有趣的测验。

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 而不是未定义。

该代码的棘手部分是 => 函数是作为默认参数值表达式的一部分创建的。在参数默认值表达式中,作用域包括左侧声明的参数,在本例中包括参数 x。因此,出于这个原因,=> 函数中的 x 实际上是第一个参数。

该函数仅使用一个参数 1 调用,因此当 => 函数被调用时就是这样 returns,给出 [2, 1, 1].

var x 声明,正如 Crowder 先生指出的那样,具有(有点奇怪,至少对我而言)产生 new x 的效果在函数作用域中,参数 x 的值被复制到其中。没有它,就只有一个(参数)。

but still I can't get why it shows 1 instead of undefined.

不只是你。这是规范中很深、很暗的部分。 :-)

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

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

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

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

  • 2 已分配给函数 body 中的变量 x。所以在函数的最后,var x2.
  • x 得到值 2 之前,
  • 1 从 var x 赋值给 y,所以在函数的最后,y1.
  • 参数x的值从未改变,所以f()在函数末尾导致1

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

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));

...I removed var x from the function body, I found that the response changed to #3...

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

以较早的 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));

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

declaring a variable twice in IIFE

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