【问题标题】:Scoping and closure oddities in javascriptjavascript中的作用域和闭包怪异
【发布时间】:2017-04-14 19:15:13
【问题描述】:

这是昨天在 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
}

有人可以向我解释一下这个东西是如何工作的吗?作为记录,它只能在非严格模式下工作

谢谢。

【问题讨论】:

  • 具体与托管有关?这个答案很好地解释了它:stackoverflow.com/questions/25111087/…
  • @CodingIntrigue:不,这不包括上面发生的事情,这是 ES2015 的 Annex B 和 TC39 的产物,它们尽最大努力引导现有代码和历史上不一致的实现的严重危险水域。 :-)
  • @T.J.Crowder 想详细解释一下吗? function f(){} 通常会在普通函数块内提升,第一个 undefined 应该打印函数 f 声明,但是现在在块内定义呢?
  • @kstratis:我正在尝试,但我想非常小心,不要给你不正确的信息。仅供参考,相关位是B.3.3.1,尤其是步骤 1.a.ii.3 中的 jiggery-pokery。但我还没有详细阅读该部分(遗憾的是,我不得不退出)所以我手头没有准备好的解释。 令人着迷的情况。
  • @T.J.Crowder 感谢您的链接,看来问题比我意识到的要复杂。

标签: javascript function ecmascript-6 closures hoisting


【解决方案1】:

我不会声称自己了解所有的细微之处,但关键在于附件 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 都有效地声明在代码 sn-p 的顶部,初始值为 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 em> 是字符串"f",被声明的函数的名称):

3。评估 FunctionDeclaration f 时,执行以下步骤代替 14.1.21 中提供的 FunctionDeclaration 评估算法:

一个。让 fenv 为正在运行的执行上下文的 VariableEnvironment。

b.让 fenvRec 成为 fenv 的 EnvironmentRecord。

c。令 benv 为正在运行的执行上下文的 LexicalEnvironment。

d。让 benvRec 成为 benv 的 EnvironmentRecord。

e。让fobj成为! benvRec.GetBindingValue(F, false).

f。履行 ! fenvRec.SetMutableBinding(F, fobj, false).

g.返回 NormalCompletion(空)。

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

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

【讨论】:

  • 您能否详细说明前两个p()?为什么他们以undefined 出现?这一点最让我烦恼;我们已经有了提升,因此 f 应该已经在那个时候定义了......
  • @kstratis 请记住,提升不会设置变量。它所做的只是更改声明,所以如果你有,比如第 10 行的var x = 5生效代码是文件顶部的var x;,然后是x = 5 在线10. 第 1-9 行的 x 仍等于 undefined,因为尚未设置该值。所以在第 4 行执行console.log(x) 将产生undefined。然而,如果你没有var x,它会产生ReferenceError
  • @kstratis:见上面 vlaz 的评论。我还添加了第二个代码 sn-p 来明确说明。
  • @kstratis:那是我在最后引用规范的那一点,它将内部f(我称之为f1)的值转移到外部f .这确实很奇怪,但他们又一次试图引导最好的路线,以避免在野外编写太多代码。
  • @kstratis:引用的位那个位,他们只是真的把它画出来了。 :-) (a) 和 (b) 基本上是在说“获取整个函数的变量环境”,而 (c) 和 (d) 是在说“获取块的词法环境”,然后 (e) 读取内部 @ 的值987654348@ (f1) 来自块的环境,并且 (f) 将该值写入变量环境中的外部 f。 (是的,规范一清二楚。)
猜你喜欢
  • 2017-06-18
  • 2010-10-12
  • 2016-03-29
  • 1970-01-01
  • 2011-01-19
  • 2014-02-13
  • 2013-01-01
  • 1970-01-01
  • 2018-09-09
相关资源
最近更新 更多