2

以下 Javascript 程序:

function f() {
  function g() { console.log(x); }
  let x = 0;
  g();  // prints 0
  x = 1;
  g();  // prints 1
  return g;
}

let g = f();
g();  // prints 1

输出:

0
1
1

所以似乎首先通过引用g捕获(因为 inside ,然后打印然后何时反弹),这意味着闭包环境看起来像,然后通过值(因为 outside ,在body末尾脱离上下文时打印) ,这意味着闭包环境看起来像.x fg()01xg{'x': x}fg()1xfg{'x': 1}

我试图将此行为与提供按引用和按值捕获的 C++ lambda 相关联,但与 Javascript 相反,不允许按引用捕获通过变成按值捕获(而是调用lambda 成为未定义的行为)。

这是对 Javascript 捕获的正确解释吗?

如果这种解释是正确的,那将清楚地解释块范围变量 ( let) 的捕获如何在for循环中工作:

let l = [];

for (let x = 0; x < 3; ++x) {
  l.push(function () { console.log(x); });
}

l[0]();  // prints 0
l[1]();  // prints 1
l[2]();  // prints 2
4

2 回答 2

2

在 JavaScript 中,当 g() 引用表达式中的变量 x 时,无论是否从 f() 内部调用 g(),实际上没有区别。只有一个变量x,每当代码运行时,获取它都是相同的内部操作g()

JavaScript 与 C++ 有很大不同。表面的化妆品相似性可能具有欺骗性。在讨论JavaScript 语义时,也很少使用“捕获”一词(根据我的经验,例如在 Stack Overflow 上),尽管规范在其详尽的描述中使用它来描述进入范围时会发生什么。这里的相关词是闭包,如“x 在 g() 的闭包中。(我对术语很草率,所以有人可能会改进我的措辞。)

更多:注意我们可以修改g()来演示,x仍然不仅可以访问获取它的值,还可以修改:

    function f() {
      function g() { console.log(x = x + 1); }
      let x = 0;
      g();  // prints 1
      x = 1;
      g();  // prints 2
      return g;
    }
    
    g = f();
    g();
    g();
    g();

变量x继续表现得像普通变量总是表现得一样。

于 2020-08-11T12:27:50.520 回答
1

简而言之

除了超出范围时它的工作方式之外,您几乎是正确的。

更多细节

JavaScript 中如何“捕获”变量?

JavaScript 使用词法环境来确定哪个函数使用哪个变量。词汇环境由环境记录表示。在你的情况下:

  • 有一个全球环境;
  • 该函数f()定义了它的词法环境,在其中x被定义,即使它在之后g()
  • 内部函数g()定义了它的词法环境,它是空的。

所以g()使用x. 由于那里没有绑定x,JavaScriptx在封闭环境中查找。由于在其中找到,xing()将使用 in 的x绑定f()。这看起来像词法范围的绑定。

如果稍后您在被调用x的环境中定义 an ,仍将绑定到in :g()g()xf()

function f() {
  function g() { console.log(x); }
  let x = 0;
  g();  // prints 0
  x = 1;
  g();  // prints 1
  return g;
}

let x = 4;
let g = f();
g();  // prints 1 (the last known value in f before returning)

在线演示

这表明绑定是静态的,并且将始终引用定义x的词法范围内的已知g()

这篇优秀的文章详细解释了它是如何工作的,并带有非常漂亮的图形。它适用于闭包(即带有执行上下文的匿名函数),但也适用于普通函数。

为什么会保留超出范围的变量的值?

如何解释这种非常特殊的行为,即x只要仍在范围内,JavaScript 将始终采用当前值(如 C++ 中的引用),而在超出范围时(当超出范围引用时x)它将采用最后一个已知值xC ++会是UB)?当变量死亡时,JavaScript 是否会将值复制到闭包中?不,比这更简单!

这与垃圾收集有关:g()返回到外部上下文。由于g()使用了xin f(),垃圾收集器会意识到这个x对象f()仍在使用中。因此,只要g()是可访问的,该xinf()将保持活动状态并对其仍处于活动状态的绑定保持可访问性。所以不需要复制值:x对象只会保留(未修改)。

作为证明它不是副本的证据,您可以研究以下代码。它在上下文中定义了第二个函数,f()能够改变 (same) x

let h;

function f() {
  function g() { console.log(x); }
  h = function () { x = 27; }
  let x = 0;
  g();  // prints 0
  x = 1;
  g();  // prints 1
  x = 3;
  return g;
}

let x = 4;
let g = f();
g();  // prints 3
h();
g();  // prints 27

在线演示

编辑:在稍微复杂的背景下解释这种现象的附加奖励文章。有趣的是,它解释说,如果不采取预防措施,这种情况可能会导致内存泄漏。

于 2020-08-11T21:19:20.357 回答