lee*_*mes 551 javascript closures scope
我的一个朋友和我正在讨论什么是JS的封闭,什么不是.我们只是想确保我们真正理解它.
我们来看看这个例子吧.我们有一个计数循环,并希望在控制台上打印计数器变量延迟.因此,我们使用setTimeout和闭包来捕获计数器变量的值,以确保它不会打印值N的N倍.
错误的解决方案,无需关闭或接近任何倒闭将是:
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
Run Code Online (Sandbox Code Playgroud)
这当然会打印10次i循环后的值,即10.
所以他的尝试是:
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2);
}, 1000)
})();
}
Run Code Online (Sandbox Code Playgroud)
按预期打印0到9.
我告诉他,他并没有使用封闭捕获i,但他坚持认为他是.我证明他没有使用闭包,将for循环体放在另一个setTimeout(将他的匿名函数传递给setTimeout),再次打印10次10.如果我将他的函数存储在a中var并在循环之后执行它同样适用,也打印10次10.所以我的论点是他并没有真正捕获它的值i,使他的版本不是一个闭包.
我的尝试是:
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000);
}
Run Code Online (Sandbox Code Playgroud)
所以我捕获i(i2在闭包中命名),但现在我返回另一个函数并传递它.在我的例子中,传递给setTimeout的函数确实捕获了i.
现在谁在使用闭包,谁不是?
请注意,两个解决方案在控制台上打印0到9都会延迟,因此它们解决了原始问题,但我们想要了解这两个解决方案中的哪一个使用闭包来实现此目的.
Aad*_*hah 643
编者按:在JavaScript中所有的功能都关闭在这个解释后.不过我们只是在识别这些功能,这是一个子集,有兴趣的有趣从理论角度.此后,除非另有说明,否则对闭包一词的任何引用都将指代该功能子集.
闭包的简单解释:
现在让我们用它来确定谁使用闭包,谁不使用闭包(为了解释我已经命名了函数):
案例1:您朋友的计划
for (var i = 0; i < 10; i++) {
(function f() {
var i2 = i;
setTimeout(function g() {
console.log(i2);
}, 1000);
})();
}
Run Code Online (Sandbox Code Playgroud)
在上面的程序中有两个功能:f和g.让我们看看它们是否是闭包:
用于f:
i2是一个局部变量.i是一个自由变量.setTimeout是一个自由变量.g是一个局部变量.console是一个自由变量.i被绑定到了全球范围.setTimeout被绑定到了全球范围.console被绑定到了全球范围.i没有关闭了通过f.setTimeout没有关闭了通过f.console没有关闭了通过f.因此该函数f不是闭包.
用于g:
console是一个自由变量.i2是一个自由变量.console被绑定到了全球范围.i2是必然的范围f.setTimeout.
console没有关闭了通过g.i2被封闭在通过g.因此,当从内部引用时,该函数g是自由变量i2(它是一个upvalue g)的闭包.setTimeout
对你不好:你的朋友正在使用关闭.内部函数是一个闭包.
案例2:您的计划
for (var i = 0; i < 10; i++) {
setTimeout((function f(i2) {
return function g() {
console.log(i2);
};
})(i), 1000);
}
Run Code Online (Sandbox Code Playgroud)
在上面的程序中有两个功能:f和g.让我们看看它们是否是闭包:
用于f:
i2是一个局部变量.g是一个局部变量.console是一个自由变量.console被绑定到了全球范围.console没有关闭了通过f.因此该函数f不是闭包.
用于g:
console是一个自由变量.i2是一个自由变量.console被绑定到了全球范围.i2是必然的范围f.setTimeout.
console没有关闭了通过g.i2被封闭在通过g.因此,当从内部引用时,该函数g是自由变量i2(它是一个upvalue g)的闭包.setTimeout
对你有好处:你正在使用一个封闭物.内部函数是一个闭包.
所以你和你的朋友都在使用闭包.停止争论.我希望我清除了闭包的概念以及如何为你们两个识别它们.
编辑:关于为什么所有函数都关闭的简单解释(信用@Peter):
首先让我们考虑以下程序(它是控件):
lexicalScope();
function lexicalScope() {
var message = "This is the control. You should be able to see this message being alerted.";
regularFunction();
function regularFunction() {
alert(eval("message"));
}
}Run Code Online (Sandbox Code Playgroud)
lexicalScope和regularFunction不封闭,从上面的定义.message得到警告,因为 regularFunction它不是一个闭包(即它可以访问其父作用域中的所有变量 - 包括message).message确实已经警告.接下来让我们考虑以下程序(它是替代方案):
var closureFunction = lexicalScope();
closureFunction();
function lexicalScope() {
var message = "This is the alternative. If you see this message being alerted then in means that every function in JavaScript is a closure.";
return function closureFunction() {
alert(eval("message"));
};
}Run Code Online (Sandbox Code Playgroud)
closureFunction的闭包.message不会被警告,因为它 closureFunction是一个闭包(即它只能在创建函数时访问所有非局部变量(参见本答案) - 这不包括).messagemessage实际上正在被警告.我们从中推断出什么?
kev*_*kev 94
根据closure定义:
"闭包"是一个表达式(通常是一个函数),它可以将自由变量与绑定这些变量的环境("关闭"表达式)结合在一起.
您正在使用closure,如果你定义一个使用它的功能之外定义变量的函数.(我们将变量称为自由变量).
他们都使用closure(即使在第一个例子中).
bri*_*out 51
一言以蔽之的Javascript闭包允许函数访问变量是在词法父函数声明.
让我们看一个更详细的解释.要理解闭包,了解JavaScript如何定义变量非常重要.
领域
在JavaScript中,范围是使用函数定义的.每个函数都定义了一个新范围.
考虑以下示例;
function f()
{//begin of scope f
var foo='hello'; //foo is declared in scope f
for(var i=0;i<2;i++){//i is declared in scope f
//the for loop is not a function, therefore we are still in scope f
var bar = 'Am I accessible?';//bar is declared in scope f
console.log(foo);
}
console.log(i);
console.log(bar);
}//end of scope f
Run Code Online (Sandbox Code Playgroud)
调用f打印
hello
hello
2
Am I Accessible?
Run Code Online (Sandbox Code Playgroud)
现在让我们考虑一下我们g在另一个函数中定义函数的情况f.
function f()
{//begin of scope f
function g()
{//being of scope g
/*...*/
}//end of scope g
/*...*/
}//end of scope f
Run Code Online (Sandbox Code Playgroud)
我们将调用f的词汇父的g.如前所述,我们现在有两个范围; 范围f和范围g.
但是一个范围是"在"另一个范围内,那么子函数范围是父函数范围的一部分吗?在父函数范围内声明的变量会发生什么?我能从子功能的范围访问它们吗?这正是关闭步骤的地方.
关闭
在JavaScript中,函数g不仅可以访问在scope中声明的任何变量,g还可以访问在父函数范围内声明的任何变量f.
考虑以下;
function f()//lexical parent function
{//begin of scope f
var foo='hello'; //foo declared in scope f
function g()
{//being of scope g
var bar='bla'; //bar declared in scope g
console.log(foo);
}//end of scope g
g();
console.log(bar);
}//end of scope f
Run Code Online (Sandbox Code Playgroud)
调用f打印
hello
undefined
Run Code Online (Sandbox Code Playgroud)
我们来看看这条线console.log(foo);.此时我们在范围内,g并且我们尝试访问foo在范围中声明的变量f.但是如前所述,我们可以访问词法父函数中声明的任何变量,这就是这里的情况; g是词汇的父母f.因此hello被打印.
我们现在看看这条线console.log(bar);.此时我们在范围内,f并且我们尝试访问bar在范围中声明的变量g.bar未在当前作用域中声明且该函数g不是父作用域f,因此bar未定义
实际上我们也可以访问在词法"祖父"函数范围内声明的变量.因此,如果函数中h定义了函数g
function f()
{//begin of scope f
function g()
{//being of scope g
function h()
{//being of scope h
/*...*/
}//end of scope h
/*...*/
}//end of scope g
/*...*/
}//end of scope f
Run Code Online (Sandbox Code Playgroud)
然后h将能够访问的功能范围内声明的所有变量h,g和f.这是通过闭包完成的.在JavaScript中,闭包允许我们访问词法父函数,词法祖父函数,词汇祖父函数等中声明的任何变量.这可以看作是一个范围链 ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... 直到最后一个没有词法父项的父函数.
窗口对象
实际上链不会停在最后一个父函数.还有一个特殊的范围; 在全球范围内.未在函数中声明的每个变量都被视为在全局范围内声明.全球范围有两个专业;
window对象的属性.因此foo,在全局范围内有两种方式声明变量; 通过不在函数中声明它或通过设置foo窗口对象的属性.
两次尝试都使用闭包
现在您已经阅读了更详细的解释,现在很明显两个解决方案都使用了闭包.但可以肯定的是,让我们来证明一下.
让我们创建一种新的编程语言; JavaScript的无闭幕.顾名思义,JavaScript-No-Closure与JavaScript完全相同,只是它不支持闭包.
换一种说法;
var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello
Run Code Online (Sandbox Code Playgroud)
好吧,让我们看看使用JavaScript-No-Closure的第一个解决方案会发生什么.
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}, 1000)
})();
}
Run Code Online (Sandbox Code Playgroud)
因此,这将undefined在JavaScript-No-Closure中打印10次.
因此第一种解决方案使用闭包.
让我们看看第二个解决方案;
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}
})(i), 1000);
}
Run Code Online (Sandbox Code Playgroud)
因此,这将undefined在JavaScript-No-Closure中打印10次.
两种解决方案都使用闭包.
编辑:假设这3个代码片段未在全局范围内定义.否则,变量foo和i将被绑定到window对象,因此通过访问window在JavaScript和JavaScript的无关闭对象.
Eri*_*pen 22
我从来没有对任何人解释这一点的方式感到满意.
理解闭包的关键是理解没有闭包的JS会是什么样的.
没有闭包,这会引发错误
function outerFunc(){
var outerVar = 'an outerFunc var';
return function(){
alert(outerVar);
}
}
outerFunc()(); //returns inner function and fires it
Run Code Online (Sandbox Code Playgroud)
一旦outerFunc返回到一个假想的关闭禁用版本的JavaScript中,对outerVar的引用将被垃圾收集并且不再留下任何内容以供引用的内部函数.
闭包本质上是特殊规则,当内部函数引用外部函数的变量时,这些规则可以使这些变量存在.对于闭包,即使在外部函数完成后也会保持引用的变量,如果这有助于您记住该点,则将其"关闭".
即使使用闭包,在没有引用其本地的内部函数的函数中的局部变量的生命周期与无闭包版本中的相同.功能完成后,当地人会收集垃圾.
一旦你在内部函数中引用了一个外部变量,但是它就像一个doorjamb被放在那些引用变量的垃圾收集方式中.
查看闭包的一种更准确的方法是,内部函数基本上使用内部作用域作为其自己的作用域.
但引用的上下文实际上是持久的,而不是快照.重复触发返回的内部函数,该函数继续递增并记录外部函数的局部变量将继续警告更高的值.
function outerFunc(){
var incrementMe = 0;
return function(){ incrementMe++; console.log(incrementMe); }
}
var inc = outerFunc();
inc(); //logs 1
inc(); //logs 2
Run Code Online (Sandbox Code Playgroud)
Jon*_*Jon 17
你们都在使用闭包.
我在这里使用维基百科的定义:
在计算机科学中,闭包(也是词法闭包或函数闭包)是函数的函数或引用以及引用环境 - 一个存储对该函数的每个非局部变量(也称为自由变量)的引用的表.闭包 - 与普通函数指针不同 - 允许函数访问那些非局部变量,即使在其直接词法范围之外调用时也是如此.
您朋友的尝试i通过获取其值并将副本存储到本地,明确地使用非本地变量i2.
您自己的尝试传递i(在调用站点的范围内)作为参数的匿名函数.到目前为止,这不是一个闭包,但是该函数返回另一个引用相同的函数i2.由于内部匿名函数内部i2不是本地函数,因此会创建一个闭包.
And*_* D. 13
你和你的朋友都使用闭包:
闭包是一种特殊的对象,它结合了两个东西:一个函数,以及创建该函数的环境.环境由创建闭包时在范围内的任何局部变量组成.
MDN:https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures
在你朋友的代码函数中function(){ console.log(i2); }定义了匿名函数的闭包,function(){ var i2 = i; ...并且可以读/写局部变量i2.
在你的代码函数中function(){ console.log(i2); }定义了函数的闭包,function(i2){ return ...并且可以读/写本地有价值的i2(在本例中声明为参数).
在这两种情况下,函数都会function(){ console.log(i2); }传入setTimeout.
另一个等价物(但内存利用率较低)是:
function fGenerator(i2){
return function(){
console.log(i2);
}
}
for(var i = 0; i < 10; i++) {
setTimeout(fGenerator(i), 1000);
}
Run Code Online (Sandbox Code Playgroud)
Ja͢*_*͢ck 10
让我们看看两种方式:
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2);
}, 1000)
})();
Run Code Online (Sandbox Code Playgroud)
声明并立即执行setTimeout()在其自己的上下文中运行的匿名函数.i通过将副本放入i2第一个来保留当前值; 它的工作原理是因为立即执行.
setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000);
Run Code Online (Sandbox Code Playgroud)
声明内部函数的执行上下文,其中i保存当前值i2; 此方法还使用立即执行来保留值.
重要
应该提到的是,两种方法之间的运行语义并不相同; 你的内部函数被传递给setTimeout()他,而他的内部函数setTimeout()自称.
将这两个代码包装在另一个代码中setTimeout()并不能证明只有第二种方法使用了闭包,开始时就不一样了.
结论
这两种方法都使用闭合,因此它归结为个人品味; 第二种方法更容易"移动"或概括.
And*_*ies 10
关闭
闭包不是函数,也不是表达式.它必须被视为函数内部使用的变量的一种"快照",并在函数内部使用.在语法上,人们应该说:'关闭变量'.
换句话说,闭包是:闭包是函数所依赖的变量的相关上下文的副本.
再一次(naïf):一个闭包可以访问未作为参数传递的变量.
请记住,这些功能概念在很大程度上取决于您使用的编程语言/环境.在JavaScript中,闭包取决于词法范围(在大多数c语言中都是如此).
因此,返回一个函数主要是返回一个匿名/未命名的函数.当函数访问变量时,不作为参数传递,并且在其(词法)范围内,已经采用了闭包.
所以,关于你的例子:
// 1
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i); // closure, only when loop finishes within 1000 ms,
}, 1000); // i = 10 for all functions
}
// 2
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i; // closure of i (lexical scope: for-loop)
setTimeout(function(){
console.log(i2); // closure of i2 (lexical scope:outer function)
}, 1000)
})();
}
// 3
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); // closure of i2 (outer scope)
}
})(i), 1000); // param access i (no closure)
}
Run Code Online (Sandbox Code Playgroud)
所有人都在使用闭包.不要将执行点与闭包混淆.如果闭包的'快照'是在错误的时刻拍摄的,那么这些值可能是意料之外的,但肯定会关闭!
小智 8
我刚才写了这篇文章,提醒自己一个闭包是什么以及它在JS中是如何工作的.
闭包是一个函数,当被调用时,它使用声明它的作用域,而不是它被调用的作用域.在javaScript中,所有函数的行为都是这样的.只要存在仍指向它们的函数,范围中的变量值就会持续存在.规则的例外是'this',它指的是函数在调用时所在的对象.
var z = 1;
function x(){
var z = 2;
y(function(){
alert(z);
});
}
function y(f){
var z = 3;
f();
}
x(); //alerts '2'
Run Code Online (Sandbox Code Playgroud)
仔细检查后,看起来你们两个都在使用封闭装置.
在你的朋友的情况下,i在匿名函数1 i2中访问,并在匿名函数2中访问,其中console.log存在.
在您的情况下,您正在访问i2匿名函数内部console.log存在.在"范围变量"下的chrome开发人员工具debugger;之前console.log和之前添加一个语句,它将告诉变量在什么范围内.
| 归档时间: |
|
| 查看次数: |
106435 次 |
| 最近记录: |