var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
它输出这个:
我的价值:3
我的价值:3
我的价值:3
而我希望它输出:
我的价值:0
我的价值:1
我的价值:2
使用事件侦听器导致运行函数的延迟时,会出现同样的问题:
var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
// as event listeners
buttons[i].addEventListener("click", function() {
// each should log its value.
console.log("My value: " + i);
});
}
...或异步代码,例如使用Promises:
// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
for (var i = 0; i < 3; i++) {
// Log `i` as soon as each promise resolves.
wait(i * 100).then(() => console.log(i));
}
这个基本问题的解决方案是什么?
好吧,问题是i
每个匿名函数中的变量都绑定到函数外部的同一个变量.
你想要做的是将每个函数中的变量绑定到函数之外的一个单独的,不变的值:
var funcs = [];
function createfunc(i) {
return function() {
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i] = createfunc(i);
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
尝试:
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function(index) {
return function() {
console.log("My value: " + index);
};
}(i));
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
编辑(2014):
我个人认为@ Aust 最近关于使用的答案.bind
是现在做这种事情的最好方法.还有LO-破折号/下划线的_.partial
,当你不需要或不想要惹bind
的thisArg
.
另一种尚未提及的方法是使用 Function.prototype.bind
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = function(x) {
console.log('My value: ' + x);
}.bind(this, i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
使用立即调用的函数表达式,这是封装索引变量的最简单,最易读的方法:
for (var i = 0; i < 3; i++) {
(function(index) {
console.log('iterator: ' + index);
//now you can also loop an ajax call here
//without losing track of the iterator value: $.ajax({});
})(i);
}
这会将迭代器发送i
到我们定义为的匿名函数中index
.这将创建一个闭包,i
保存变量以供以后在IIFE中的任何异步功能中使用.
派对迟到了,但我今天正在探讨这个问题,并注意到许多答案并没有完全解决Javascript如何处理范围,这基本上归结为这个问题.
正如许多其他人提到的那样,问题是内部函数引用了相同的i
变量.那么为什么我们不在每次迭代时只创建一个新的局部变量,而是使用内部函数引用呢?
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '' + msg + '
';};
var funcs = {};
for (var i = 0; i < 3; i++) {
var ilocal = i; //create a new local variable
funcs[i] = function() {
console.log("My value: " + ilocal); //each should reference its own local variable
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
随着ES6现在得到广泛支持,这个问题的最佳答案已经改变.ES6 为这种确切的情况提供了关键字let
和const
关键字.我们可以let
像使用这样设置一个循环范围变量,而不是搞乱闭包.
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
val
然后指向一个特定于循环特定转向的对象,并返回正确的值而不使用额外的闭包表示法.这显然简化了这个问题.
const
与let
初始赋值后变量名不能反弹到新引用的附加限制类似.
浏览器支持现在适用于针对最新版浏览器的用户.const
/ let
目前支持最新的Firefox,Safari,Edge和Chrome.Node也支持它,你可以利用像Babel这样的构建工具在任何地方使用它.你可以在这里看到一个有效的例子:http://jsfiddle.net/ben336/rbU4t/2/
文件在这里:
常量
让
但要注意,IE9-IE11和Edge在Edge 14支持之前let
却出现了上述错误(它们i
每次都没有创建新的,所以上面的所有函数都会像我们使用的那样记录3 var
).Edge 14最终做对了.
另一种说法是,i
函数中的函数在执行函数时受到约束,而不是创建函数的时间.
创建闭包时,i
是对外部作用域中定义的变量的引用,而不是创建闭包时的副本.它将在执行时进行评估.
大多数其他答案提供了通过创建另一个不会为您更改值的变量来解决的方法.
我想我会添加一个清晰的解释.对于一个解决方案,就个人而言,我会选择Harto,因为从这里的答案来看,这是最不言自明的方式.发布的任何代码都可以使用,但我选择封闭工厂而不必写一堆注释来解释为什么我要声明一个新变量(Freddy和1800's)或者有奇怪的嵌入式闭包语法(apphacker).
你需要了解的是javascript中变量的范围是基于函数的.这是一个重要的区别,而不是c#,你有块范围,只是将变量复制到for内的一个将起作用.
将它包装在一个函数中,将函数评估为像apphacker的答案一样返回函数将完成这一操作,因为变量现在具有函数范围.
还有一个let关键字而不是var,允许使用块范围规则.在那种情况下,在for中定义变量就可以了.也就是说,由于兼容性,let关键字不是一个实用的解决方案.
var funcs = {};
for (var i = 0; i < 3; i++) {
let index = i; //add this
funcs[i] = function() {
console.log("My value: " + index); //change to the copy
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
这是该技术的另一种变体,类似于Bjorn(apphacker),它允许您在函数内部分配变量值,而不是将其作为参数传递,有时可能更清晰:
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function() {
var index = i;
return function() {
console.log("My value: " + index);
}
})();
}
请注意,无论使用何种技术,index
变量都会变成一种静态变量,绑定到内部函数的返回副本.即,在调用之间保留对其值的更改.它可以非常方便.
这描述了在JavaScript中使用闭包的常见错误.
考虑:
function makeCounter() { var obj = {counter: 0}; return { inc: function(){obj.counter ++;}, get: function(){return obj.counter;} }; } counter1 = makeCounter(); counter2 = makeCounter(); counter1.inc(); alert(counter1.get()); // returns 1 alert(counter2.get()); // returns 0
对于每次makeCounter
调用,都会{counter: 0}
导致创建一个新对象.此外,obj
还会创建新副本以引用新对象.因此,counter1
和counter2
相互独立的.
在循环中使用闭包很棘手.
考虑:
var counters = []; function makeCounters(num) { for (var i = 0; i < num; i++) { var obj = {counter: 0}; counters[i] = { inc: function(){obj.counter++;}, get: function(){return obj.counter;} }; } } makeCounters(2); counters[0].inc(); alert(counters[0].get()); // returns 1 alert(counters[1].get()); // returns 1
请注意,counters[0]
和counters[1]
是不是独立的.事实上,他们的运作方式相同obj
!
这是因为obj
在循环的所有迭代中只有一个共享副本,可能是出于性能原因.即使{counter: 0}
在每次迭代中创建一个新对象,相同的副本obj
也只会通过对最新对象的引用进行更新.
解决方案是使用另一个辅助函数:
function makeHelper(obj) { return { inc: function(){obj.counter++;}, get: function(){return obj.counter;} }; } function makeCounters(num) { for (var i = 0; i < num; i++) { var obj = {counter: 0}; counters[i] = makeHelper(obj); } }
这是有效的,因为函数作用域中的局部变量以及函数参数变量在进入时都会分配新的副本.
有关详细讨论,请参阅JavaScript闭包陷阱和用法
最简单的解决方案是,
而不是使用:
var funcs = []; for(var i =0; i<3; i++){ funcs[i] = function(){ alert(i); } } for(var j =0; j<3; j++){ funcs[j](); }
提醒"2",共3次.这是因为在for循环中创建的匿名函数共享相同的闭包,并且在该闭包中,值i
是相同的.使用它来防止共享关闭:
var funcs = []; for(var new_i =0; new_i<3; new_i++){ (function(i){ funcs[i] = function(){ alert(i); } })(new_i); } for(var j =0; j<3; j++){ funcs[j](); }
这背后的想法是,使用IIFE(立即调用的函数表达式)封装for循环的整个主体,并new_i
作为参数传递并将其捕获为i
.由于匿名函数是立即执行的,i
因此匿名函数内定义的每个函数的值都不同.
这个解决方案似乎适合任何这样的问题,因为它需要对遇到此问题的原始代码进行最小的更改.事实上,这是设计,它应该不是一个问题!
没有数组
没有额外的循环
for (var i = 0; i < 3; i++) { createfunc(i)(); } function createfunc(i) { return function(){console.log("My value: " + i);}; }
http://jsfiddle.net/7P6EN/
这是一个使用的简单解决方案forEach
(回到IE9):
var funcs = [];
[0,1,2].forEach(function(i) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
})
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
打印:
My value: 0 My value: 1 My value: 2
OP显示的代码的主要问题i
是在第二个循环之前永远不会读取.为了演示,想象一下在代码中看到错误
funcs[i] = function() { // and store them in funcs throw new Error("test"); console.log("My value: " + i); // each should log its value. };
在funcs[someIndex]
执行之前实际上不会发生错误()
.使用相同的逻辑,显然在i
此之前也不会收集值.一旦原始循环结束,i++
将i
导致其值3
导致条件i < 3
失败并且循环结束.在这一点上,i
是3
等时funcs[someIndex]()
使用,并i
进行评估,这是3 -每一次.
为了解决这个问题,您必须i
在遇到问题时进行评估.请注意,这已经以funcs[i]
(有3个唯一索引)的形式发生.有几种方法可以捕获此值.一种是将其作为参数传递给函数,该函数已经以几种方式显示在此处.
另一个选择是构造一个能够关闭变量的函数对象.这可以这样完成
jsFiddle Demo
funcs[i] = new function() { var closedVariable = i; return function(){ console.log("My value: " + closedVariable); }; };
JavaScript函数"关闭"它们在声明时可以访问的范围,并保留对该范围的访问权限,即使该范围中的变量发生更改.
var funcs = []
for (var i = 0; i < 3; i += 1) {
funcs[i] = function () {
console.log(i)
}
}
for (var k = 0; k < 3; k += 1) {
funcs[k]()
}
在阅读了各种解决方案之后,我想补充一点,这些解决方案的工作原理是依赖于范围链的概念.这是JavaScript在执行期间解析变量的方式.
每个函数定义形成一个范围,包括var
由其声明的所有局部变量arguments
.
如果我们在另一个(外部)函数中定义了内部函数,则会形成一个链,并将在执行期间使用
执行函数时,运行时通过搜索范围链来评估变量.如果可以在链的某个点找到变量,它将停止搜索并使用它,否则它将一直持续到达到属于的全局范围window
.
在初始代码中:
funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = function inner() { // function inner's scope contains nothing console.log("My value: " + i); }; } console.log(window.i) // test value 'i', print 3
当funcs
执行时,范围链将是function inner -> global
.由于i
无法找到变量function inner
(既没有声明使用var
也没有作为参数传递),它继续搜索,直到i
最终在全局范围中找到值window.i
.
通过将它包装在外部函数中,可以显式定义像harto那样的辅助函数,也可以像Bjorn那样使用匿名函数:
funcs = {}; function outer(i) { // function outer's scope contains 'i' return function inner() { // function inner, closure created console.log("My value: " + i); }; } for (var i = 0; i < 3; i++) { funcs[i] = outer(i); } console.log(window.i) // print 3 still
当funcs
执行时,现在范围链将是function inner -> function outer
.这个时间i
可以在外部函数的范围中找到,该范围在for循环中执行3次,每次都i
正确绑定值.它不会使用window.i
内部执行时的值.
更多细节可以在这里找到
它包括我们在这里创建闭包的常见错误,以及为什么我们需要关闭和性能考虑.
通过ES6的新功能,可以管理块级别范围:
var funcs = []; for (let i = 0; i < 3; i++) { // let's create 3 functions funcs[i] = function() { // and store them in funcs console.log("My value: " + i); // each should log its value. }; } for (let j = 0; j < 3; j++) { funcs[j](); // and now let's run each one to see }
OP问题中的代码替换let
为var
.
我很惊讶没有人建议使用该forEach
函数来更好地避免(重新)使用局部变量.事实上,for(var i ...)
由于这个原因,我根本就不再使用了.
[0,2,3].forEach(function(i){ console.log('My value:', i); }); // My value: 0 // My value: 2 // My value: 3
//编辑使用forEach
而不是地图.
这个问题真的展示了JavaScript的历史!现在我们可以避免使用箭头函数进行块作用域,并使用Object方法直接从DOM节点处理循环.
const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())
原始示例不起作用的原因是您在循环中创建的所有闭包都引用了相同的帧.实际上,在一个对象上只有一个i
变量有3个方法.他们都打印出相同的价值.
首先,了解这段代码的错误:
var funcs = []; for (var i = 0; i < 3; i++) { // let's create 3 functions funcs[i] = function() { // and store them in funcs console.log("My value: " + i); // each should log its value. }; } for (var j = 0; j < 3; j++) { funcs[j](); // and now let's run each one to see }
这里当funcs[]
数组被初始化时,i
正在递增,funcs
数组被初始化并且func
数组的大小变为3,所以i = 3,
.现在,当funcs[j]()
调用它时,它再次使用变量i
,该变量已经增加到3.
现在要解决这个问题,我们有很多选择.以下是其中两个:
我们可以初始化i
用let
或初始化一个新的变量index
与let
和使其等于i
.因此,当进行调用时,index
将使用它并且其范围将在初始化之后结束.对于呼叫,index
将再次初始化:
var funcs = []; for (var i = 0; i < 3; i++) { let index = i; funcs[i] = function() { console.log("My value: " + index); }; } for (var j = 0; j < 3; j++) { funcs[j](); }
其他选项可以引入一个tempFunc
返回实际函数:
var funcs = []; function tempFunc(i){ return function(){ console.log("My value: " + i); }; } for (var i = 0; i < 3; i++) { funcs[i] = tempFunc(i); } for (var j = 0; j < 3; j++) { funcs[j](); }
使用闭包结构,这将减少你的额外for循环.你可以在一个for循环中完成它:
var funcs = []; for (var i = 0; i < 3; i++) { (funcs[i] = function() { console.log("My value: " + i); })(i); }
我们将检查,当您声明
var
并let
逐个实际发生时.
var
现在按F12打开Chrome控制台窗口并刷新页面.在数组中扩展每3个函数.您将看到一个名为.Expand 的属性.您将看到一个被调用的数组对象,展开该对象.您将找到声明为对象的属性,其值为3.[[Scopes]]
"Global"
'i'
结论:
当您'var'
在函数外部声明变量时,它将变为全局变量(您可以通过键入i
或
window.i
在控制台窗口中进行检查.它将返回3).
除非您调用函数,否则您声明的不可靠函数将不会调用并检查函数内的值.
调用函数时,console.log("My value: " + i)
从其Global
对象获取值并显示结果.
现在更换'var'
用'let'
做同样的事情,转到范围.现在你将看到两个对象"Block"
和"Global"
.现在展开Block
对象,你会看到'i'在那里被定义,奇怪的是,对于每个函数,值if i
是不同的(0,1,2).
结论:
当你使用'let'
函数外部但在循环内部声明变量时,这个变量将不是一个全局变量,它将成为一个Block
只能用于同一个函数的级别变量.这就是我们得到i
不同价值的原因.当我们调用函数时为每个函数.
有关近距离工作的更多细节,请浏览精彩的视频教程https://youtu.be/71AtaJpJHw0