提问人:nickf 提问时间:4/15/2009 最后编辑:3limin4t0rnickf 更新时间:5/15/2023 访问量:478269
JavaScript 闭包内部循环 – 简单的实际示例
JavaScript closure inside loops – simple practical example
问:
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);
});
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>
...或异步代码,例如使用 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));
}
在 和 循环中也很明显:for in
for of
const arr = [1,2,3];
const fns = [];
for (var i in arr){
fns.push(() => console.log("index:", i));
}
for (var v of arr){
fns.push(() => console.log("value:", v));
}
for (const n of arr) {
var obj = { number: n }; // or new MyLibObject({ ... })
fns.push(() => console.log("n:", n, "|", "obj:", JSON.stringify(obj)));
}
for(var f of fns){
f();
}
这个基本问题的解决方案是什么?
答:
尝试:
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-dash/underscore's 当你不需要或不想弄乱 's 时。_.partial
bind
thisArg
评论
}(i));
i
index
好吧,问题在于每个匿名函数中的变量都绑定到函数外部的同一变量。i
ES6 解决方案:let
ECMAScript 6 (ES6) 引入了新的关键字,其作用域与基于 - 的变量不同。例如,在具有基于 - 的索引的循环中,通过循环的每次迭代都将有一个具有循环范围的新变量,因此您的代码将按预期工作。有很多资源,但我推荐 2ality 的块范围帖子作为一个很好的信息来源。let
const
var
let
i
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
但请注意,IE9-IE11 和 Edge 14 之前的 Edge 支持,但上述内容是错误的(它们不会每次都创建一个新的,因此上面的所有函数都会记录 3,就像我们使用Edge 14 终于做对了。let
i
var
ES5.1 解决方案:forEach
随着该函数的相对广泛可用性(2015 年),值得注意的是,在主要涉及对值数组进行迭代的情况下,提供了一种干净、自然的方式来为每次迭代获取不同的闭包。也就是说,假设你有某种包含值(DOM 引用、对象等)的数组,并且出现了设置特定于每个元素的回调的问题,你可以这样做:Array.prototype.forEach
.forEach()
var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
// ... code code code for this one element
someAsynchronousFunction(arrayElement, function() {
arrayElement.doSomething();
});
});
这个想法是,与循环一起使用的回调函数的每次调用都将是它自己的闭包。传入该处理程序的参数是特定于迭代的特定步骤的数组元素。如果它在异步回调中使用,则它不会与在迭代的其他步骤中建立的任何其他回调发生冲突。.forEach
如果您碰巧在jQuery中工作,则该函数将为您提供类似的功能。$.each()
经典解决方案:闭合
您要做的是将每个函数中的变量绑定到函数外部的单独不变值:
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]();
}
由于 JavaScript 中没有块作用域 - 只有函数作用域 - 通过将函数创建包装在新函数中,您可以确保“i”的值保持预期。
评论
function createfunc(i) { return function() { console.log("My value: " + i); }; }
i
Function.bind()
.bind()
.bind()
this
i
.bind()
原始示例不起作用的原因是,您在循环中创建的所有闭包都引用了同一帧。实际上,在一个对象上具有 3 个方法,只有一个变量。它们都打印出相同的值。i
您需要了解的是 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]();
}
评论
另一种说法是,函数中的 在执行函数时是绑定的,而不是在创建函数时绑定的。i
创建闭包时,是对外部作用域中定义的变量的引用,而不是创建闭包时的副本。它将在执行时进行评估。i
大多数其他答案都提供了通过创建另一个变量来变通的方法,该变量不会为您更改值。
只是为了清楚起见,我会添加一个解释。就个人而言,对于解决方案,我会选择 Harto 的解决方案,因为从这里的答案来看,这是最不言自明的方式。发布的任何代码都可以工作,但我会选择闭包工厂,而不是必须写一堆注释来解释为什么我要声明一个新变量(Freddy 和 1800 年代)或具有奇怪的嵌入式闭包语法(apphacker)。
这是该技术的另一种变体,类似于 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
评论
var
return
var
return
这描述了在 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);
}
}
这之所以有效,是因为直接分配了函数作用域中的局部变量以及函数参数变量 入境时的新副本。
评论
var obj = {counter: 0};
随着 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
现在,针对最新版本的浏览器的用户提供浏览器支持。/ 目前在最新的 Firefox、Safari、Edge 和 Chrome 中受支持。Node 也支持它,你可以利用 Babel 等构建工具在任何地方使用它。您可以在此处查看一个工作示例:http://jsfiddle.net/ben336/rbU4t/2/const
let
文档在这里:
但请注意,IE9-IE11 和 Edge 14 之前的 Edge 支持,但上述内容是错误的(它们不会每次都创建一个新的,因此上面的所有函数都会记录 3,就像我们使用Edge 14 终于做对了。let
i
var
评论
最简单的解决方案是,
而不是使用:
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 循环
for (var i = 0; i < 3; i++) {
createfunc(i)();
}
function createfunc(i) {
return function(){console.log("My value: " + i);};
}
评论
另一种尚未提及的方法是使用 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]();
}
更新
正如 @squint 和 @mekdev 所指出的,通过先在循环外部创建函数,然后在循环内绑定结果,可以获得更好的性能。
function log(x) {
console.log('My value: ' + x);
}
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = log.bind(this, i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
评论
_.partial
.bind()
ECMAScript 6 功能将在很大程度上过时。此外,这实际上在每次迭代中创建两个函数。首先是匿名的,然后是生成的匿名的。更好的用途是在循环之外创建它,然后在循环内创建它。.bind()
.bind()
bind
this
使用立即调用的函数表达式,这是将索引变量括起来的最简单、最易读的方法:
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);
}
这会将迭代器发送到匿名函数中,我们将该函数定义为 。这将创建一个闭包,其中变量被保存,以便以后在 IIFE 中的任何异步功能中使用。i
index
i
评论
i
index
index
i
var funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function(index) { return function() {console.log('iterator: ' + index);}; })(i); }; for (var j = 0; j < 3; j++) { funcs[j](); }
.forEach()
forEach()
var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });
OP 显示的代码的主要问题是,在第二个循环之前永远不会被读取。为了演示,想象一下在代码中看到错误i
funcs[i] = function() { // and store them in funcs
throw new Error("test");
console.log("My value: " + i); // each should log its value.
};
在执行之前,该错误实际上不会发生。使用相同的逻辑,很明显,直到此时才收集 的值。一旦原始循环完成,其值将导致条件失败和循环结束。在这一点上,是和所以当被使用时,并被评估,它是 3 - 每次。funcs[someIndex]
()
i
i++
i
3
i < 3
i
3
funcs[someIndex]()
i
要克服这一点,您必须在遇到这种情况时进行评估。请注意,这已经以(其中有 3 个唯一索引)的形式发生。有几种方法可以捕获此值。一种是将其作为参数传递给函数,该函数已在此处以多种方式显示。i
funcs[i]
另一种选择是构造一个函数对象,该对象将能够关闭变量。这样可以做到这一点
funcs[i] = new function() {
var closedVariable = i;
return function(){
console.log("My value: " + closedVariable);
};
};
下面是一个简单的解决方案,它使用(工作到IE9):forEach
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
在阅读了各种解决方案之后,我想补充一点,这些解决方案之所以有效,是因为依赖于范围链的概念。这是 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
执行时,现在作用域链将是 。这个时间可以在外部函数的作用域中找到,该作用域在 for 循环中执行 3 次,每次都正确绑定了值。它不会使用 internal 执行时的值。funcs
function inner -> function outer
i
i
window.i
更多细节可以在这里
找到 它包括在循环中创建闭包的常见错误,以及为什么我们需要闭包和性能考虑。
评论
Array.prototype.forEach(function callback(el) {})
forEach
el
令我惊讶的是,还没有人建议使用该函数来更好地避免(重新)使用局部变量。事实上,由于这个原因,我根本不再使用了。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
评论
.forEach()
如果你实际上没有映射任何东西,这是一个更好的选择,Daryl 在你发布前 7 个月就建议了这一点,所以没有什么可惊讶的。
聚会有点晚了,但我今天在探讨这个问题,并注意到许多答案并没有完全解决 Javascript 如何处理范围,这基本上就是归根结底的原因。
因此,正如许多其他人所提到的,问题在于内部函数引用了相同的变量。那么,我们为什么不在每次迭代时创建一个新的局部变量,并让内部函数引用它呢?i
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
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]();
}
就像以前一样,每个内部函数都输出了分配给的最后一个值,现在每个内部函数只输出分配给 的最后一个值。但是每次迭代不应该有自己的吗?i
ilocal
ilocal
事实证明,这就是问题所在。每次迭代都共享相同的范围,因此第一次迭代之后的每次迭代都只是覆盖。来自 MDN:ilocal
重要提示:JavaScript 没有块作用域。与块一起引入的变量的范围限定为包含的函数或脚本,并且设置它们的效果会持续到块本身之外。换言之,块语句不引入作用域。尽管“独立”块是有效的语法,但您不希望在 JavaScript 中使用独立块,因为如果您认为它们在 C 或 Java 中执行类似此类块的工作,它们不会执行您认为它们执行的操作。
重申强调:
JavaScript 没有块作用域。随块引入的变量的范围限定为包含函数或脚本
我们可以通过在每次迭代中声明它之前进行检查来看到这一点:ilocal
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
console.log(ilocal);
var ilocal = i;
}
这正是这个错误如此棘手的原因。即使你重新声明了一个变量,Javascript 也不会抛出错误,JSLint 甚至不会抛出警告。这也是为什么解决这个问题的最好方法是利用闭包,这本质上是这样的想法,即在 Javascript 中,内部函数可以访问外部变量,因为内部作用域“包围”了外部作用域。
这也意味着内部函数“保留”外部变量并使其保持活动状态,即使外部函数返回也是如此。为了利用这一点,我们创建并调用一个包装函数,纯粹是为了创建一个新作用域,在新作用域中声明,并返回一个内部函数,该函数使用(更多解释见下文):ilocal
ilocal
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = (function() { //create a new scope using a wrapper function
var ilocal = i; //capture i into a local var
return function() { //return the inner function
console.log("My value: " + ilocal);
};
})(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
在包装函数中创建内部函数会为内部函数提供一个只有它才能访问的私有环境,即“闭包”。因此,每次我们调用包装函数时,我们都会创建一个新的内部函数,该函数具有自己的独立环境,确保变量不会相互冲突和覆盖。一些小的优化给出了许多其他 SO 用户给出的最终答案:ilocal
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
return function() { //return the inner function
console.log("My value: " + ilocal);
};
}
更新
随着 ES6 现在成为主流,我们现在可以使用 new 关键字来创建块范围的变量:let
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
funcs[i] = function() {
console.log("My value: " + i); //each should reference its own local variable
};
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
funcs[j]();
}
看看现在是多么容易!有关更多信息,请参阅此答案,我的信息基于该答案。
评论
let
const
let
i=0; while(i < 100) { setTimeout(function(){ window.open("https://www.bbc.com","_self") }, 3000); setTimeout(function(){ window.open("https://www.cnn.com","_self") }, 3000); i++ }
i
这是异步代码经常遇到的问题,变量是可变的,在进行函数调用时,使用的代码将被执行并突变到其最后一个值,这意味着在循环中创建的所有函数都将创建一个闭包,并且将等于 3(循环的上限 + 1。i
i
i
i
for
解决此问题的方法是创建一个函数,该函数将保存每次迭代的值并强制复制(因为它是基元,如果它对您有帮助,请将其视为快照)。i
i
您可以将声明性模块用于数据列表,例如 query-js(*)。在这些情况下,我个人认为声明式方法不那么令人惊讶
var funcs = Query.range(0,3).each(function(i){
return function() {
console.log("My value: " + i);
};
});
然后,您可以使用第二个循环并获得预期的结果,或者您可以这样做
funcs.iterate(function(f){ f(); });
(*)我是query-js的作者,因此偏向于使用它,所以不要把我的话作为对所述库的建议,只是为了声明性方法:)
评论
Query.range(0,3)
我更喜欢使用函数,它在创建伪范围时有自己的闭包:forEach
var funcs = [];
new Array(3).fill(0).forEach(function (_, i) { // creating a range
funcs[i] = function() {
// now i is safely incapsulated
console.log("My value: " + i);
};
});
for (var j = 0; j < 3; j++) {
funcs[j](); // 0, 1, 2
}
这看起来比其他语言的范围更丑陋,但恕我直言,它比其他解决方案更可怕。
评论
还有另一种解决方案:与其创建另一个循环,不如将 绑定到 return 函数。this
var funcs = [];
function createFunc(i) {
return function() {
console.log('My value: ' + i); //log value of i.
}.call(this);
}
for (var i = 1; i <= 5; i++) { //5 functions
funcs[i] = createFunc(i); // call createFunc() i=5 times
}
通过绑定它,也解决了这个问题。
首先,了解这段代码有什么问题:
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
}
这里当数组被初始化时,正在递增,数组被初始化,数组的大小变为 3,所以 .
现在,当调用 时,它再次使用变量 ,该变量已经增加到 3。funcs[]
i
funcs
func
i = 3,
funcs[j]()
i
现在为了解决这个问题,我们有很多选择。以下是其中两个:
我们可以用 初始化或初始化一个新变量,并使其等于 。因此,在进行调用时,将使用,其作用域将在初始化后结束。而对于调用,将再次初始化:
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](); }
你的代码不起作用,因为它的作用是:
Create variable `funcs` and assign it an empty array;
Loop from 0 up until it is less than 3 and assign it to variable `i`;
Push to variable `funcs` next function:
// Only push (save), but don't execute
**Write to console current value of variable `i`;**
// First loop has ended, i = 3;
Loop from 0 up until it is less than 3 and assign it to variable `j`;
Call `j`-th function from variable `funcs`:
**Write to console current value of variable `i`;**
// Ask yourself NOW! What is the value of i?
现在的问题是,调用函数时变量的值是多少?因为第一个循环是以 为条件创建的,所以当条件为 false 时,它会立即停止,因此它是 。i
i < 3
i = 3
您需要了解,在创建函数时,不会执行任何代码,只会保存以备后用。因此,当它们稍后被调用时,解释器会执行它们并问:“当前值是多少?i
因此,您的目标是首先保存 to function 的值,然后再将函数保存到 。例如,这可以通过以下方式完成:i
funcs
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function(x) { // and store them in funcs
console.log("My value: " + x); // each should log its value.
}.bind(null, i);
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
这样,每个函数都有自己的变量,我们将其设置为每次迭代的值。x
x
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
。
评论
const
提供相同的结果,当变量的值不会更改时,应使用。但是,在Firefox中,for循环的初始值设定项内部的使用不正确,尚未得到修复。它不是在块内声明,而是在块外声明,这会导致对变量的重新声明,这反过来又会导致错误。在Firefox中正确实现了初始化器内部的使用,因此无需担心。const
let
让我们利用新功能
。因此,不再是闭包的变量,而只是文本的一部分:i
var funcs = [];
for (var i = 0; i < 3; i++) {
var functionBody = 'console.log("My value: ' + i + '");';
funcs[i] = new Function(functionBody);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
评论
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]()
}
上面数组中的每个函数都在全局范围内关闭(全局,仅仅是因为这恰好是它们声明的作用域)。
稍后,将调用这些函数,记录全局范围内的最新值。这就是关闭的魔力和挫败感。i
“JavaScript 函数关闭了它们声明的范围,即使该范围内的变量值发生变化,也保留对该范围的访问。”
使用而不是通过在每次循环运行时创建一个新作用域,为每个要关闭的函数创建一个单独的作用域来解决此问题。其他各种技术对额外的功能做同样的事情。let
var
for
var funcs = []
for (let i = 0; i < 3; i += 1) {
funcs[i] = function () {
console.log(i)
}
}
for (var k = 0; k < 3; k += 1) {
funcs[k]()
}
(let
使变量限定块范围。块用大括号表示,但在 for 循环的情况下,初始化变量(在我们的例子中)被认为是在大括号中声明的。i
评论
i
for
i
funcs[j]
i
i
使用闭包结构,这将减少额外的 for 循环。您可以在单个 for 循环中执行此操作:
var funcs = [];
for (var i = 0; i < 3; i++) {
(funcs[i] = function() {
console.log("My value: " + i);
})(i);
}
许多解决方案似乎是正确的,但他们没有提到它被称为 Currying
,这是一种函数式编程设计模式,适用于这种情况。比绑定快 3-10 倍,具体取决于浏览器。
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = curryShowValue(i);
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
function curryShowValue(i) {
return function showValue() {
console.log("My value: " + i);
}
}
查看不同浏览器的性能提升。
评论
计数器是原始的
让我们按如下方式定义回调函数:
// ****************************
// COUNTER BEING A PRIMITIVE
// ****************************
function test1() {
for (var i=0; i<2; i++) {
setTimeout(function() {
console.log(i);
});
}
}
test1();
// 2
// 2
超时完成后,它将为两者打印 2。这是因为回调函数根据定义函数的词法范围访问该值。
为了在定义回调时传递和保留该值,我们可以创建一个闭包,以在调用回调之前保留该值。这可以按如下方式完成:
function test2() {
function sendRequest(i) {
setTimeout(function() {
console.log(i);
});
}
for (var i = 0; i < 2; i++) {
sendRequest(i);
}
}
test2();
// 1
// 2
现在,它的特别之处在于“原语是按值传递和复制的。因此,当定义闭包时,它们会保留上一个循环中的值。
计数器是对象
由于闭包可以通过引用访问父函数变量,因此这种方法与基元不同。
// ****************************
// COUNTER BEING AN OBJECT
// ****************************
function test3() {
var index = { i: 0 };
for (index.i=0; index.i<2; index.i++) {
setTimeout(function() {
console.log('test3: ' + index.i);
});
}
}
test3();
// 2
// 2
因此,即使为作为对象传递的变量创建了闭包,也不会保留循环索引的值。这是为了表明对象的值不会被复制,而它们可以通过引用访问。
function test4() {
var index = { i: 0 };
function sendRequest(index, i) {
setTimeout(function() {
console.log('index: ' + index);
console.log('i: ' + i);
console.log(index[i]);
});
}
for (index.i=0; index.i<2; index.i++) {
sendRequest(index, index.i);
}
}
test4();
// index: { i: 2}
// 0
// undefined
// index: { i: 2}
// 1
// undefined
这个问题已经有很多有效的答案。不过,使用函数式方法的人并不多。以下是使用该方法的替代解决方案,该方法适用于回调和闭包:forEach
let arr = [1,2,3];
let myFunc = (val, index) => {
console.log('val: '+val+'\nindex: '+index);
};
arr.forEach(myFunc);
这个问题真的展示了JavaScript的历史!现在,我们可以避免使用箭头函数进行块范围限制,并使用 Object 方法直接从 DOM 节点处理循环。
const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())
const buttons = document.getElementsByTagName("button");
Object
.keys(buttons)
.map(i => buttons[i].addEventListener('click', () => console.log(i)));
<button>0</button><br>
<button>1</button><br>
<button>2</button>
我们将检查,当您一一声明时实际会发生什么。
var
let
Case1 : 使用 var
<script>
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function () {
debugger;
console.log("My value: " + i);
};
}
console.log(funcs);
</script>
现在,按 F12 打开 chrome 控制台窗口并刷新页面。
在数组内每 3 个函数消耗一次。您将看到一个名为 的属性。展开那个。你会看到一个
数组对象,展开该对象。您将找到一个声明到值为 3 的对象中的属性。[[Scopes]]
"Global"
'i'
结论:
- 当您使用函数外部声明变量时,它将成为全局变量(您可以通过键入或在控制台窗口中检查。它将返回 3)。
'var'
i
window.i
- 您声明的匿名函数不会调用并检查函数内部的值,除非您调用 功能。
- 调用函数时,从其对象中获取值并显示
结果。
console.log("My value: " + i)
Global
CASE2 : 使用 let
现在将 替换为'var'
'let'
<script>
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function () {
debugger;
console.log("My value: " + i);
};
}
console.log(funcs);
</script>
做同样的事情,去范围。现在您将看到两个对象和 .现在展开对象,你
将看到 'i' 在那里被定义,奇怪的是,对于每个函数,值 if 是不同的 (0 , 1, 2)。"Block"
"Global"
Block
i
结论:
当你使用函数外部但在循环内部声明变量时,这个变量将不是一个全局变量
变量,它将成为一个级别变量,仅适用于同一函数。这就是为什么,我们
当我们调用函数时,每个函数的值都不同。'let'
Block
i
有关Closer如何工作的更多详细信息,请浏览精彩的视频教程 https://youtu.be/71AtaJpJHw0
虽然这个问题已经过时了,但我还有另一个相当有趣的解决方案:
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i]();
}
变化是如此之小,以至于几乎很难看出我做了什么。我将第二个迭代器从 j 切换到 i。这以某种方式及时刷新了 i 的状态,为您提供所需的结果。我这样做是偶然的,但考虑到之前的答案,这是有道理的。
我写这篇文章是为了指出这个微小但非常重要的区别。希望这有助于为像我这样的其他学习者消除一些困惑。
注意:我不分享这个,因为我认为这是正确的答案。这是一个不稳定的解决方案,在某些情况下可能会破裂。实际上,我很惊讶它确实有效。
评论
i
i
i = 0; funcs[0](); i = 1; funcs[1](); ..
i
3
0,1,2,3
j=0
funcs[0]
假设您不使用 ES6; 您可以使用 IIFE:
var funcs = [];
for (var i = 0; i < 13; i++) {
funcs[i] = (function(x) {
console.log("My value: " + i)
})(i);
}
但情况会有所不同。
还行。我通读了所有的答案。尽管这里有一个很好的解释 - 我只是无法让它工作。于是我去网上找了看。https://dzone.com/articles/why-does-javascript-loop-only-use-last-value 的人有一个答案,这里没有介绍。所以我想我会发布一个简短的例子。这对我来说更有意义。
它的长处和短处是 LET 命令很好,但现在才被使用。但是,LET 命令实际上只是一个 TRY-CATCH 组合。这一直工作到IE3(我相信)。使用 TRY-CATCH 组合 - 生活简单而美好。这可能是 EMCScript 人员决定使用它的原因。它也不需要 setTimeout() 函数。所以没有时间浪费。基本上,每个 FOR 循环都需要一个 TRY-CATCH 组合。下面是一个示例:
for( var i in myArray ){
try{ throw i }
catch(ii){
// Do whatever it is you want to do with ii
}
}
如果您有多个 FOR 循环,则只需为每个循环放置一个 TRY-CATCH 组合即可。此外,就我个人而言,我总是使用我使用的任何 FOR 变量的双字母。所以“ii”代表“i”,依此类推。我在例程中使用此技术将鼠标悬停命令发送到不同的例程。
为什么不在创建第一个(也是唯一一个)循环后立即调用每个函数,例如:
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.
};
funcs[i]();// and now let's run each one to see
}
评论
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function(param) { // and store them in funcs
console.log("My value: " + param); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](j); // and now let's run each one to see with j
}
这证明了 javascript 在“闭包”和“非闭包”的工作方式方面是多么丑陋。
在以下情况下:
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.
};
}
funcs[i] 是一个全局函数,'console.log(“My value: ” + 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);
}
由于 JavaScript 的这种扭曲闭包设计,“console.log(”我的值:“ + i);” 正在从外部函数“createfunc(i)”打印 i
这一切都是因为 javascript 无法像 C 编程语言那样在函数中设计像样的“静态”变量!
只需将 var 关键字更改为 let。
var 是函数范围的。
let 是块范围。
当你开始编写代码时,for 循环将迭代并将 i 的值赋值为 3,该值将在整个代码中保持 3。我建议你阅读更多关于节点中的作用(let,var,const等)的信息
funcs = [];
for (let i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] =async function() { // and store them in funcs
await 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
}
asyncIterable = [1,2,3,4,5,6,7,8];
(async function() {
for await (let num of asyncIterable) {
console.log(num);
}
})();
评论
使用 let(blocked-scope) 而不是 var。
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
在 的支持下,最好的方法是在这种确切的情况下使用 和 关键字。所以变量 get 和 在循环结束时,所有 ...的 的值都会更新,我们可以用来设置一个循环范围变量,如下所示:ES6
let
const
var
hoisted
i
closures
let
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
在 ES5 之前,这个问题只能使用闭包来解决。
但是现在在 ES6 中,我们有块级范围变量。将 var 更改为 let in first for 循环将解决问题。
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 (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
如果您在循环而不是循环时遇到此类问题,例如:while
for
var i = 0;
while (i < 5) {
setTimeout(function() {
console.log(i);
}, i * 1000);
i++;
}
收盘于当前值的技术略有不同。在块内声明一个块范围的变量,并为其分配电流。然后,无论在何处异步使用该变量,请替换为新的块范围变量:const
while
i
i
var i = 0;
while (i < 5) {
const thisIterationI = i;
setTimeout(function() {
console.log(thisIterationI);
}, i * 1000);
i++;
}
对于不支持块范围变量的旧浏览器,可以使用名为 的 IIFE:i
var i = 0;
while (i < 5) {
(function(innerI) {
setTimeout(function() {
console.log(innerI);
}, innerI * 1000);
})(i);
i++;
}
如果要调用的异步操作恰好如上所述,您还可以使用第三个参数进行调用,以指示要调用传递函数的参数:setTimeout
setTimeout
var i = 0;
while (i < 5) {
setTimeout(
(thisIterationI) => { // Callback
console.log(thisIterationI);
},
i * 1000, // Delay
i // Gets passed to the callback; becomes thisIterationI
);
i++;
}
评论
let
const
刚刚替换为var
let
var funcs = []
// change `var` to `let`
for (let i = 0; i < 3; i++) {
funcs[i] = function(){
console.log("My value:", i); //change to the copy
}
}
for (var j = 0; j < 3; j++) {
funcs[j]()
}
评论
i
let
var