当引用在 Javascript 中脱离上下文时,按引用捕获是否会变成按值捕获?
Does capture by reference turn into capture by value when the reference goes out of context in Javascript?
以下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
首先通过引用 捕获 x
(因为在 f
、g()
中打印 0
then 1
when x
is rebound),这意味着g
闭包环境看起来像{'x': x}
,然后by value(因为在 f
之外,当 x
在 f
正文末尾脱离上下文时,g()
打印 1
),这意味着 g
闭包环境看起来像 {'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
在 JavaScript 中,当 g() 引用表达式中的变量 x 时,无论 g() 是否从 f() 内部调用,实际上都没有区别。只有一个变量 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
的行为与普通变量的行为始终如一。
简而言之
你几乎是正确的,除了当它超出范围时它是如何工作的。
更多详情
如何在 JavaScript 中“捕获”变量?
JavaScript 使用 lexical environments to determine which function uses which variable. Lexical environments are represented by environment records。你的情况:
- 有全局环境;
- 函数
f()
定义了它的词法环境,其中定义了x
,即使是在g()
; 之后
- 内部函数
g()
定义了它的词法环境,它是空的。
所以 g()
使用 x
。由于那里没有 x
的绑定,因此 JavaScript 在封闭环境中查找 x
。既然在里面找到了,那么g()
中的x
就会使用f()
中的x
的绑定。这看起来像 lexically scoped 绑定。
如果稍后在调用 g()
的环境中定义 x
,g()
仍将绑定到 f()
中的 x
:
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)
这表明绑定是静态的,并且将始终引用在定义 g()
的词法范围内已知的 x
。
这个 excellent article 详细解释了它是如何工作的,图形非常漂亮。它适用于闭包(即具有执行上下文的匿名函数),但也适用于普通函数。
为什么会保留超出范围的变量值?
如何解释这种非常特殊的行为,只要 x
仍在范围内(就像 C++ 中的引用),JavaScript 将始终采用 x
的当前值,而它当 x
超出范围时(当 C++ 中的超出范围引用将是 UB 时)将采用最后一个已知值?当变量消失时,JavaScript 是否将值复制到闭包中?不,比这还简单!
这与garbage collection有关:g()
返回到外部上下文。由于g()
使用了f()
中的x
,垃圾收集器会意识到f()
的这个x
对象仍在使用中。因此,只要 g()
可访问,f()
中的 x
将保持活动状态并保持可访问,以便其仍然处于活动状态的绑定。因此无需复制值:x
对象将保持不变(未修改)。
作为不是抄袭的证明,你可以研究下面的代码。它在 f()
的上下文中定义了第二个函数,该函数能够更改(相同的)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
编辑: 附加 bonus article 在稍微复杂的上下文中解释了这种现象。有趣的是,它解释说如果不采取预防措施,这种情况会导致内存泄漏。
以下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
首先通过引用 捕获 x
(因为在 f
、g()
中打印 0
then 1
when x
is rebound),这意味着g
闭包环境看起来像{'x': x}
,然后by value(因为在 f
之外,当 x
在 f
正文末尾脱离上下文时,g()
打印 1
),这意味着 g
闭包环境看起来像 {'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
在 JavaScript 中,当 g() 引用表达式中的变量 x 时,无论 g() 是否从 f() 内部调用,实际上都没有区别。只有一个变量 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
的行为与普通变量的行为始终如一。
简而言之
你几乎是正确的,除了当它超出范围时它是如何工作的。
更多详情
如何在 JavaScript 中“捕获”变量?
JavaScript 使用 lexical environments to determine which function uses which variable. Lexical environments are represented by environment records。你的情况:
- 有全局环境;
- 函数
f()
定义了它的词法环境,其中定义了x
,即使是在g()
; 之后
- 内部函数
g()
定义了它的词法环境,它是空的。
所以 g()
使用 x
。由于那里没有 x
的绑定,因此 JavaScript 在封闭环境中查找 x
。既然在里面找到了,那么g()
中的x
就会使用f()
中的x
的绑定。这看起来像 lexically scoped 绑定。
如果稍后在调用 g()
的环境中定义 x
,g()
仍将绑定到 f()
中的 x
:
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)
这表明绑定是静态的,并且将始终引用在定义 g()
的词法范围内已知的 x
。
这个 excellent article 详细解释了它是如何工作的,图形非常漂亮。它适用于闭包(即具有执行上下文的匿名函数),但也适用于普通函数。
为什么会保留超出范围的变量值?
如何解释这种非常特殊的行为,只要 x
仍在范围内(就像 C++ 中的引用),JavaScript 将始终采用 x
的当前值,而它当 x
超出范围时(当 C++ 中的超出范围引用将是 UB 时)将采用最后一个已知值?当变量消失时,JavaScript 是否将值复制到闭包中?不,比这还简单!
这与garbage collection有关:g()
返回到外部上下文。由于g()
使用了f()
中的x
,垃圾收集器会意识到f()
的这个x
对象仍在使用中。因此,只要 g()
可访问,f()
中的 x
将保持活动状态并保持可访问,以便其仍然处于活动状态的绑定。因此无需复制值:x
对象将保持不变(未修改)。
作为不是抄袭的证明,你可以研究下面的代码。它在 f()
的上下文中定义了第二个函数,该函数能够更改(相同的)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
编辑: 附加 bonus article 在稍微复杂的上下文中解释了这种现象。有趣的是,它解释说如果不采取预防措施,这种情况会导致内存泄漏。