javascript 中的范围界定和闭包异常

Scoping and closure oddities in javascript

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

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.

几乎是奇怪的扭曲

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

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

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是字符串"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).

回想一下,variable 环境是函数范围的,但是 lexical 环境更受限制(到块)。

当涉及到尝试在函数声明{无效 | 的地方规范化函数声明时未指定} (选择你的术语),TC39 有一条非常 危险的导航路径,试图标准化行为同时不破坏可能依赖的现有代码关于过去特定于实现的行为(这是相互排斥的,但 TC39 正在努力取得平衡)。