(function(x, f = () => x) {
var x = 2;
console.log(f());
})(1)
似乎是最简单的代码,从逻辑上讲,它应该返回2。但它返回1。
我相信如果将这种行为f
声明为:
- IIFE型
f = (() => x)()
- 简单的视图分配
f = x
在这些情况下,该值f
将被计算一次并且已经是常数(很明显,那时f
不可能将其作为函数调用,但现在不是这样)。
但是,由于f
是一个我认为应该根据 的值以不同方式执行的函数,x
所以代码的行为对我来说并不清楚。
我想当被调用时,该函数f
将返回 value x
,此时它已经等于2(被上面的行覆盖)。但该函数仍然返回1。为什么?
经过反复试验,发现如果f
在函数体中重写,
(function(x, f = () => x) {
f = () => x;
var x = 2;
console.log(f());
})(1)
然后代码开始按我的预期工作。由此我得出结论,这里的决定性因素是定义函数的位置(即,它被定义为参数的默认值)。但是,再一次,为什么这很重要?
在写这个问题时,我更进一步,还发现x
. 如果代码看起来像这样(没有关键字var
),
(function(x, f = () => x) {
x = 2;
console.log(f());
})(1)
那么结论又是合乎逻辑的 - 2。
同时,我假设的第一件事是,由于函数是在参数字符串中定义的(作为默认值),所以它的值x
不是作为函数体x
中变量的值,而是作为值。但是无论是在场还是在场,价值本身都不会改变。这是函数执行的结果——是的。所以这是另一回事。唯一的问题是什么?argument[0]
var
arguments[0]
为什么第一个示例的输出
1
而不是输出的问题的答案2
在于函数的形式参数。规范的当前修订版(版本)在第9.2.12 节 FunctionDeclarationInstantiation中描述了函数的执行(更准确地说,初始化)。它还以纯文本形式表示,在函数初始化期间词法环境及其条目发生的情况取决于形式参数:
特别是,如果函数的形参中至少有一个参数定义了默认值(简称为“默认参数”),则为函数体内的所有声明创建一个单独的词法环境。
正是出于这个原因,表达式在专门创建的词法环境中
var x = 2
定义了一个新变量。但是,由于函数定义
f
引用了另一个 Lexical Environment(为形参创建的那个),所以当它被调用时,将取变量的值,x
记录在其中的 Lexical Environment 的记录中该功能已定义f
。简单地说,1
.此外,规范将其定位为“功能”。在算法 9.2.12 的步骤27.a中,她说为函数体内的声明创建了一个单独的词法环境,以便形式参数之间的闭包无法访问函数体内声明的变量:
这正是问题中给出的示例中发生的情况。正是从这一点来看,规范试图保护我们(尽管这种“关心”的好处颇有争议,但现在不是这样了)。
当函数的形参中没有一个默认参数时,情况完全相反:
在这种情况下,函数体中的声明和形参之间的声明都指向同一个词法环境。因此,表达式
var x = 2
准确地重新定义了存在于同一范围内的变量x
。但是,如果
x
函数体内的变量是使用关键字声明的let
,那么在这种情况下,我们会得到一个语法错误:这是由于关键字的机制
let
,特别是不允许重复声明这样的变量。实际上,这是一个简化的解释。在某些情况下,即使使用关键字
let
(即使形式参数中没有默认参数),仍然会为此类声明创建一个新的词法环境。附录 B.3.3.1 中有关此内容的更多信息。但是,在这个答案的范围内,这些功能并不是特别有趣,所以我将省略它们。最后一件事,关于问题的最后一个例子:
在这种情况下,答案
2
是由于缺少关键字var
。由于
x
这里的变量不是在新的词法环境中声明的(为什么是新的,上面读过),而是覆盖了x
在父词法环境中声明的变量(顺便说一下,对创建的词法环境的外部词法环境的引用)函数体内的声明准确地指向在创建“附加”词法环境之前就存在的词法环境,参见第 27.b) 条,函数调用f
将返回已经重新定义的变量值x
。