关于功能:javascript中的范围和关闭奇怪

Scoping and closure oddities in javascript

这是昨天在TC39上提出的。 你可以在这里找到要点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var p = () => console.log(f);
{
  p(); // undefined
  console.log(f); // function f(){}

  f = 1;

  p(); // undefined
  console.log(f); // 1

  function f(){}

  p(); // 1
  console.log(f); // 1

  f = 2;

  p(); // 1
  console.log(f); // 2
}

有人可以向我解释这件事是如何运作的吗? 对于记录,它仅在非严格模式下工作。

谢谢。


我不会声称理解所有细微之处,但关键是关于附件B§B.3.3.1的几乎奇怪的扭曲。

该代码实际上是这样的,其中f1是特定于块的词法环境的f的第二个副本(因此let):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var p = () => console.log(f);
{
  let f1 = function f(){};;           // Note hoisting
  p(); // undefined
  console.log(f1); // function f(){}

  f1 = 1;

  p(); // undefined
  console.log(f1); // 1

  var f = f1;                          // !!!

  p(); // 1
  console.log(f1); // 1

  f1 = 2;

  p(); // 1
  console.log(f1); // 2
}

当然,由于var提升,pf都在代码片段的顶部有效声明,初始值为undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var f = undefined;
var p = undefined;
p = () => console.log(f);
{
  let f1 = function f(){};;           // Note hoisting
  p(); // undefined
  console.log(f1); // function f(){}

  f1 = 1;

  p(); // undefined
  console.log(f1); // 1

  f = f1;                              // !!!

  p(); // 1
  console.log(f1); // 1

  f1 = 2;

  p(); // 1
  console.log(f1); // 2
}

B.3.3.1中的关键位是它将内部f(我称之为f1)的值传递给外部的一个(在下面,F是字符串"f",要声明的函数的名称):

3. When the FunctionDeclaration f is evaluated, perform the following steps in place of the FunctionDeclaration Evaluation algorithm provided in 14.1.21:

a. Let fenv be the running execution context's VariableEnvironment.

b. Let fenvRec be fenv's EnvironmentRecord.

c. Let benv be the running execution context's LexicalEnvironment.

d. Let benvRec be benv's EnvironmentRecord.

e. Let fobj be ! benvRec.GetBindingValue(F, false).

f. Perform ! fenvRec.SetMutableBinding(F, fobj, false).

g. Return NormalCompletion(empty).

回想一下,变量环境是功能范围的,但词汇环境更受限制(到块)。

在尝试将函数声明标准化为{invalid |的地方时未指定}(选择你的术语),TC39有一个非常危险的导航路径,试图标准化行为,同时不破坏可能依赖于过去的实现特定行为的现有代码(这是相互排斥的,但TC39正试图冲帐)。