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
我的价值: 3
而我希望它输出:
我的值: 0
我的值: 1
我的值: 2
当运行函数的延迟是由使用事件侦听器引起时, 也会出现同样的问题:
var buttons = document.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) { // let's create 3 functions
buttons[i].addEventListener("click", function() { // as event listeners
console.log("My value: " + i); // each should log its value.
});
}
0
1
2
...或异步代码, 例如使用承诺:
// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
for(var i = 0; i < 3; i++){
wait(i * 100).then(() => console.log(i)); // Log `i` as soon as each promise resolves.
}
这个基本问题的解决方案是什么?
问题是, 在 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 中没有块作用域 (仅函数作用域), 因此通过将函数创建包装在一个新函数中, 可以确保 "i" 的值保持您的预期值。
随着函数的相对广泛可用 ( Array.prototype.forEach
在 2015 年), 值得注意的是, 在那些主要涉及通过一组值进行迭代的情况下, .forEach()
为每次迭代提供了一种干净、自然的方法来获得一个独特的闭包。也就是说, 假设您有某种数组, 其中包含值 (DOM 引用、对象、任何内容), 并且设置特定于每个元素的回调时出现了问题, 则可以执行以下操作:
var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
// ... code code code for this one element
someAsynchronousFunction(arrayElement, function() {
arrayElement.doSomething();
});
});
其想法是, 与循环一起使用的回调函数的每次调用 .forEach
都将是其自己的闭包。传递给该处理程序的参数是特定于迭代的特定步骤的数组元素。如果它在异步回调中使用, 它将不会与在迭代的其他步骤中建立的任何其他回调发生冲突。
如果您恰好在 jQuery 中工作, 该 $.each()
函数将为您提供类似的功能。
let
ECMAScript 6 (ES6) 引入了与 let
const
基于变量不同的新关键字和关键字 var
。例如, 在具有 let
基于索引的循环中, 通过该循环的每个迭代都将有一个新 i
值, 该值的作用域在循环中, 因此您的代码将按预期方式工作。有很多资源, 但我建议2alityd 的块范围帖子作为一个很好的信息来源。
< 3;="" i++)="" {="" funcs[i]="function()" {="" console.log("my="" value:="" "="" +="" i);="" };="" }="">
不过, 请注意, IE9-IE11 和 Edge 在边缘 14 之前支持 let
, 但错误 (它们不会每次都创建新 i
的, 因此上面的所有函数都会像我们使用时一样 var
记录 3)。边缘 14 终于得到了它的权利。
尝试:
var funcs = [];
< 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
的答案是现在做这种事情的最好方法。_.partial
当你不需要或不想和它乱搞的时候, 也有 lo-dashn' bind
thisArg
下划线。
另一种尚未被提及的方法是使用Function.prototype.bind
var funcs = {};
< 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);
}
使用"半插入函数表达式", 最简单、最可读的方法来包含索引变量:
< 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
闭包中, 变量将保存以供以后在 IIF 中的任何异步功能中使用。
有点晚了党, 但我今天正在探讨这个问题, 并注意到许多答案并没有完全解决 Javascript 如何对待作用域, 这本质上就是这归结于什么。
因此, 正如许多其他提到的, 问题是内部函数引用的是相同的 i
变量。那么, 为什么我们不在每次迭代中创建一个新的局部变量, 并拥有内部函数引用呢?
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '' + msg + '';};
就像以前一样, 每个内部函数输出分配给的最后一个值 i
, 现在每个内部函数只输出分配给的最后一个值 ilocal
。但每个迭代不应该都有自己的 ilocal
吗?
原来, 这就是问题所在。每个迭代共享相同的范围, 因此第一次迭代之后的每个迭代都只是覆盖 ilocal
。从Mdn:
重要提示: JavaScript 没有块作用域。与块一起引入的变量的作用域为包含函数或脚本, 设置它们的效果保持在块本身之外。换句话说, 块语句不会引入作用域。尽管 "独立" 块是有效的语法, 但您不希望在 JavaScript 中使用独立块, 因为它们不会像您认为的那样执行, 如果您认为它们在 C 或 Java 中执行过类似于这些块的操作。
重申强调:
JavaScript 没有块作用域。与块一起引入的变量的作用域为包含函数或脚本
我们可以通过 ilocal
在每次迭代中声明之前检查来看到这一点:
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '' + msg + '';};
这正是为什么这个错误是如此棘手。即使您正在重新声明变量, Javascript 也不会引发错误, JSLint 甚至不会引发警告。这也是解决这个问题的最好方法是利用闭包, 这实质上是这样的想法, 即在 Javascript 中, 内部函数可以访问外部变量, 因为内部作用域 "封闭" 了外部作用域。
这也意味着内部函数 "守住" 外部变量并使其保持活动状态, 即使外部函数返回也是如此。为了利用这一点, 我们创建并调用一个包装函数, 纯粹是为了创建一个新的作用域, ilocal
在新的作用域中声明, 并返回使用的内部函数 ilocal
(下面的更多解释):
//overwrite console.log() so you can see the console output
console.log =
由于 ES6 现在得到了广泛的支持, 这个问题的最佳答案已经改变。 ES6 为此 let
const
具体情况提供了关键字。 我们可以只使用如下所示 let
的循环作用域变量来设置闭包, 而不是搞乱闭闭包。
var funcs = [];
< 3;="" i++)="" {="" funcs[i]="function()" {="" console.log("my="" value:="" "="" +="" i);="" };="" }="">
val
然后将指向特定于循环特定回合的对象, 并返回不需要附加闭包表示法的正确值。 这显然大大简化了此问题。
const
类似 let
于在初始分配后变量名不能反弹到新引用的附加限制。
浏览器支持现在是在这里为那些针对最新版本的浏览器。const
/ let
目前支持最新的 firefox、Safari、Edge 和 chrome。它在 Node 中也受支持, 您可以通过利用巴贝尔这样的构建工具在任何地方使用它。 您可以在这里看到一个工作示例: http://jsfiddle.net/ben336/rbU4t/2/
这里的文档:
不过, 请注意, IE9-IE11 和 Edge 在边缘 14 之前支持 let
, 但错误 (它们不会每次都创建新 i
的, 因此上面的所有函数都会像我们使用时一样 var
记录 3)。边缘 14 终于得到了它的权利。
另一种说法是, i
函数中的函数在执行函数时是绑定的, 而不是在创建函数时绑定的。
创建闭包时, i
是对外部作用域中定义的变量的引用, 而不是创建闭包时的副本。它将在执行时进行评估。
大多数其他答案都提供了通过创建另一个不会更改您的值的变量来解决的方法。
我只是想补充一个清晰的解释。对于一个解决方案, 就我个人而言, 我会和哈托一起去, 因为从这里的答案来看, 这是最不言而喻的解决方法。任何发布的代码将工作, 但我会选择关闭工厂, 而不必写一堆评论, 以解释为什么我宣布一个新的变量 (弗雷迪和 1800 ' s) 或有奇怪的嵌入式关闭语法 (apphacker)。
您需要了解的是 javascript 中变量的范围是基于函数的。这是一个重要的区别, 比说 c#, 你有块范围, 只是复制变量到一个里面的 for 将工作。
将其包装在一个计算返回像 apphacker 回答这样的函数的函数中就可以了, 因为变量现在有了函数范围。
还有一个 let 关键字而不是 var, 它允许使用块作用域规则。在这种情况下, 定义 for 内部的变量就可以了。也就是说, 由于兼容性的原因, let 关键字不是一个实用的解决方案。
var funcs = {};
< 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), 它允许您在函数内分配变量值, 而不是将其作为参数传递, 有时可能会更清楚:
< 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)
{
< 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)
{
< 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;"><3; j++){="" funcs[j]();="" }="">
发出 "2" 警报 3 次。这是因为在循环、共享相同闭包中创建的匿名函数, 在该闭包中, 其值是 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;"><3; j++){="" funcs[j]();="" }="">
这背后的想法是, 用Iife (半调用函数表达式) 封装 for 循环的整个主体, 并将 new_i
其作为参数传递并捕获为. i
由于匿名函数是立即执行的, 因此 i
在匿名函数中定义的每个函数的值都是不同的。
此解决方案似乎适合任何此类问题, 因为它将需要对遭受此问题的原始代码进行最少的更改。其实, 这是设计的, 根本不应该是个问题!
无数组
没有额外的循环
< 3;="" i++)="" {="" createfunc(i)();="" }="" function="" createfunc(i)="" {="" return="" function(){console.log("my="" value:="" "="" +="" i);};="" }="">
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 <>
失败和循环结束的值。在这一点上 i
, 3
是, 所以当 funcs[someIndex]()
使用, 并 i
被计算, 它是 3-每次。
要克服这一点, 您必须 i
在遇到时进行评估。请注意, 这种情况已经以 funcs[i]
(其中有 3 个唯一索引) 的形式发生。有几种方法可以捕获此值。一种是将其作为参数传递给一个函数, 该函数已经在这里以几种方式显示。
另一个选项是构造一个函数对象, 该函数对象将能够在变量上关闭。这可以通过这种方式来实现
funcs[i] = new function() {
var closedVariable = i;
return function(){
console.log("My value: " + closedVariable);
};
};
下面是一个使用的简单解决方案 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.
};
})
< 3;="" j++)="" {="" funcs[j]();="" and="" now="" let's="" run="" each="" one="" to="" see="" }="">
指纹:
My value: 0 My value: 1 My value: 2
JavaScript 函数 "关闭" 它们在声明时可以访问的范围, 并保留对该范围的访问权限, 即使该作用域中的变量发生了变化。
var funcs = []
for (var i = 0; i < 3; i += 1) {
funcs[i] = function () {
console.log(i)
}
}
上面数组中的每个函数都在全局范围内关闭 (全局, 原因很简单, 就是因为这恰好是它们在中声明的作用域)。
稍后调用这些函数, 记录全局范围中最新的值 i
。这就是封闭的魔力和挫折感。
"JavaScript 函数关闭在其中声明的作用域, 并保留对该作用域的访问权限, 即使该作用域内的变量值发生更改也是如此。
使用 let
而不是 var
通过在每次循环运行时创建一个新作用 for
域来解决此问题, 为每个函数创建一个要关闭的分隔作用域。其他各种技术使用额外的功能执行相同的操作。
var funcs = []
for (let i = 0; i < 3; i += 1) {
funcs[i] = function () {
console.log(i)
}
}
( let
使变量块的作用域。块用大括号表示, 但在 for 循环的情况下, 在我们的例子中, 初始化变量 i
被认为是在大括号中声明的。
在阅读了各种解决方案之后, 我想补充一点, 这些解决方案之所以起作用, 是因为依赖于作用域链的概念。这是 JavaScript 在执行过程中解析变量的方式。
var
arguments
组成的作用域。window
域。在初始代码中:
funcs = {};
< 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);
};
}
< 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 = [];
< 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())
const buttons = document.getElementsByTagName("button");
Object
.keys(buttons)
.map(i => buttons[i].addEventListener('click', () => console.log(i)));
0
1
2
首先, 了解此代码的错误之全:
var funcs = [];
< 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 = [];
< 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);
};
}
< 3;="" i++)="" {="" funcs[i]="tempFunc(i);" }="" for="" (var="" j="0;" j="">< 3;="" j++)="" {="" funcs[j]();="" }="">
我们将检查, 当你申报的时候, 实际会发生什么
var
let
, 一个接一个。
var
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function () {
debugger;
console.log("My value: " + i);
};
}
console.log(funcs);
现在, 按 F12 打开您的镀铬控制台窗口并刷新页面。 将阵列内的每 3 个函数都扩展。您将看到一个名为 " [[Scopes]]
属性"。展开那个。您将看到一个调用的数组对象 "Global"
, 展开该对象。您将找到一个 'i'
声明到具有值 3 的对象中的属性。
结论:
'var'
, 它将成为全局变量 (您可以通过键入 i
或 window.i
在控制台窗口中进行检查。它将返回 3)。console.log("My value: " + i)
从其对象中获取值 Global
并显示结果。现在 'var'
, 替换为'let'
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function () {
debugger;
console.log("My value: " + i);
};
}
console.log(funcs);
做同样的事情, 去范围。现在, 您将看到两个对象 "Block"
和 "Global"
。现在展开 Block
对象, 您将看到 "i" 在那里定义, 奇怪的是, 对于每个函数, 如果值 i
不同 (0, 1, 2)。
结论:
'let'
即使在函数之外但在循环内部也可以声明变量, 则此变量将不是全局变量, 它将成为仅 Block
适用于同一函数的级别变量。这就是为什么, i
当我们调用函数时, 我们得到的每个函数的值都是不同的。
有关如何更接近的工作的更多细节, 请通过真棒视频教程https://youtu.be/71AtaJpJHw0
原始示例不起作用的原因是, 您在循环中创建的所有闭包都引用了相同的帧。实际上, 在一个只有一个变量的对象上有 3 个方法 i
。它们都打印出了相同的值。
使用闭包结构, 这将减少您的循环额外。您可以在一个循环中执行此操作:
var funcs = [];
< 3;="" i++)="" {="" (funcs[i]="function()" {="" console.log("my="" value:="" "="" +="" i);="" })(i);="" }="">
我更喜欢使用 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);
};
});
< 3;="" j++)="" {="" funcs[j]();="" 0,="" 1,="" 2="" }="">
这看起来比其他语言中的范围更丑陋, 但 IMHO 比其他解决方案更不可怕。
var funcs = [];
< 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="" }="">
您可以对数据列表 (如查询 j(*)) 使用声明性模块。在这种情况下, 我个人发现声明性方法并不那么令人惊讶
var funcs = Query.range(0,3).each(function(i){
return function() {
console.log("My value: " + i);
};
});
然后, 您可以使用第二个循环, 并获得预期的结果, 或者你可以做
funcs.iterate(function(f){ f(); });
(*)我是 query-j 的作者, 因此偏向于使用它, 所以不要把我的话作为对所述库的建议, 只针对声明性方法:)
许多解决方案看起来是正确的, 但他们没有提到 Currying
它被称为它, 这是一种功能性编程设计模式, 适用于这里这样的情况. 根据浏览器的不同, 比绑定快 3-10 倍。
var funcs = [];
< 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);="" }="" }="">
查看不同浏览器中的性能提升。
您的代码不起作用, 因为它的作用是:
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?
现在的问题是, 当调用函数时, 变量的值是 i
什么?因为第一个循环是在条件的情况下创建 i <>
的, 所以当条件为 false 时, 它就会立即停止, 所以是这样 i = 3
的。
您需要了解, 在创建函数时, 它们的代码都不会执行, 只保存到以后。因此, 当稍后调用它们时, 解释器执行它们并询问: "当前的值是 i
多少?"
所以, 你的目标是首先保存函数的值 i
, 然后才保存函数 funcs
。例如, 可以这样做:
var funcs = [];
< 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
每次迭代中将其设置为值。
这只是解决此问题的多种方法之一。
还有一个解决方案: 而不是创建另一个循环, 只需将返回 this
函数绑定到返回函数即可。
var funcs = [];
function createFunc(i) {
return function() {
console.log('My value: ' + i); //log value of i.
}.call(this);
}
通过绑定这一点, 也解决了这个问题。
作为一个成功的计数器
让我们定义回调函数, 如下所示:
// ****************************
// 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);
});
}
< 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="">