循环中的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. }; } 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. }); }
<button>0</button><br> <button>1</button><br> <button>2</button>
这个基本问题的解决scheme是什么?
那么,问题是,在你的每个匿名函数中,variablesi
被绑定到函数之外的同一个variables。
你想要做的就是将每个函数中的variables绑定到函数之外的一个单独的,不变的值:
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++) { funcs[j](); // and now let's run each one to see }
尝试:
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):
我个人认为@澳大利亚最近对使用.bind
回答是现在做这种事情的最好方法。 当你不需要或者不想混乱bind
的_.partial
时,还有lo-dash /下划线的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](); }
的jsfiddle
UPDATE
正如@squint和@mekdev指出的那样,通过先在循环外部创build函数,然后在循环中绑定结果,可以获得更好的性能。
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](); }
的jsfiddle
使用立即调用的函数expression式 ,包含索引variables的最简单和最可读的方式:
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
的匿名函数中。 这将创build一个闭包,其中variablesi
被保存起来,以便以后在IIFE中的任何asynchronousfunction中使用。
晚了点,但我今天正在探讨这个问题,并注意到许多答案并没有完全解决Javascript如何对待示波器,这本质上是什么这个归结。
所以像其他人提到的那样,问题在于内部函数引用相同的i
variables。 那么为什么我们不是每次迭代都创build一个新的局部variables,而是使用内部函数引用呢?
//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](); }
随着ES6得到广泛支持,这个问题的最佳答案已经改变。 ES6为这个确切的情况提供了let
和const
关键字。 我们可以使用let
来设置一个循环范围variables,像这样:
var funcs = []; for (let i = 0; i < 3; i++) { funcs[i] = function() { console.log("My value: " + i); }; }
val
将指向特定于该特定循环的对象,并且将返回正确的值而不用额外的闭合符号。 这显然极大地简化了这个问题。
const
类似于let
的额外限制,即在初始赋值之后variables名不能被重新引用到一个新引用。
浏览器支持现在是针对最新版浏览器的。 目前最新的Firefox,Safari,Edge和Chrome都支持const
/ let
。 它在Node中也受支持,您可以利用像Babel这样的构build工具在任何地方使用它。 你可以在这里看到一个工作的例子: http : //jsfiddle.net/ben336/rbU4t/2/
这里的文档:
- 常量
- 让
但是请注意,在Edge 14支持之前的IE9-IE11和Edge let
得到了上面的错误(他们每次都不会创build一个新的i
,所以上面的所有函数都会像使用var
一样logging3)。 边缘14终于得到它的权利。
另一种说法是,函数中的函数在执行函数的时候被绑定,而不是函数的创build时间。
在创build闭包时, 我是对外部作用域中定义的variables的引用,而不是创build闭包时的副本。 它将在执行时评估。
大多数其他的答案提供了解决方法,通过创build另一个variables,不会改变你的价值。
只是想我会添加一个解释清晰。 对于一个解决scheme,我个人会select哈托,因为这是从这里的答案中最自我解释的方式。 任何代码都可以工作,但是我会select一个闭包工厂,而不必写一堆评论来解释为什么我要声明一个新variables(Freddy和1800's)或者有一个怪异的embedded闭包语法(apphacker)。
你需要了解的是javascript中的variables的范围是基于函数的。 这是一个重要的区别比说你有块范围的c#,只是复制variables为一个里面的将工作。
把它包装在一个函数中,返回像apphacker的答案一样的函数,将会起到一定的作用,因为variables现在具有函数范围。
还有一个let关键字而不是var,这将允许使用块作用域规则。 在这种情况下,在for内部定义一个variables就可以了。 这就是说,let关键字不是一个实际的解决scheme,因为兼容性。
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),它可以让你在函数内部赋值variables值,而不是把它作为parameter passing,有时可能会更清楚一些:
for (var i = 0; i < 3; i++) { funcs[i] = (function() { var index = i; return function() { console.log("My value: " + index); } })(); }
请注意,无论您使用哪种技术, index
variables都会成为一种静态variables,绑定到内部函数的返回副本。 也就是说,在通话之间保留对其值的更改。 它可以非常方便。
这描述了在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}
生成一个新的对象。 另外,还创build了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
副本,这可能是出于性能原因。 尽pipe{counter: 0}
在每次迭代中都创build一个新对象,但obj
的同一副本只会通过对最新对象的引用进行更新。
解决scheme是使用另一个辅助函数:
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); } }
这是可行的,因为函数作用域中的局部variables以及函数参数variables在进入时被分配新的副本。
有关详细的讨论,请参阅JavaScript封闭的陷阱和使用
最简单的解决scheme将是
而不是使用这个
var funcs = []; for(var i =0; i<3; i++){ funcs[i] = function(){ alert(i); } } for(var j =0; j<3; j++){ funcs[j](); }
哪个改变“2”,三次。 这是因为for循环中创build的匿名函数共享相同的闭包,并且在闭包中,i的值是相同的。 使用这个来防止共享closures,
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 (立即调用的函数expression式)封装整个for循环体,并将“new_i”作为参数并将其捕获为“i”。 由于匿名函数是立即执行的,因此匿名函数中定义的每个函数的“i”值是不同的。 这个解决scheme似乎适合任何这样的问题,因为这将需要对原来的代码进行最小的修改, 事实上,这是devise,它不应该是一个问题!
试试这个较短的一个
-
没有arrays
-
没有额外的循环
for (var i = 0; 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 < 3
失败,循环结束。 在这一点上, i
是3
,所以当funcs[someIndex]()
被使用,并且i
被评估,每次都是3。
为了克服这个问题,你必须评估i
遇到的情况。 请注意,这已经以funcs[i]
(其中有三个唯一索引)的forms发生。 有几种方法可以捕获这个值。 一个是把它作为一个parameter passing给一个已经在这里以几种方式显示的函数。
另一个select是构造一个可以closuresvariables的函数对象。 这可以做到这一点
jsFiddle Demo
funcs[i] = new function() { var closedVariable = i; return function(){ console.log("My value: " + closedVariable); }; };
这是一个简单的解决scheme,使用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
JavaScript函数“closures”它们在声明时可以访问的范围,并保留对该范围的访问权限,即使该范围内的variables发生更改。
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]() }
在阅读了各种解决scheme后,我想补充一下,这些解决scheme工作的原因是依赖于范围链的概念。 这是JavaScript在执行过程中parsingvariables的方式。
- 每个函数定义形成一个由
var
和其arguments
声明的所有局部variables组成的范围。 - 如果我们在另一个(外部)函数内部定义了内部函数,这将形成一个链,并在执行期间使用
- 当函数被执行时,运行时通过search作用域链来评估variables。 如果一个variables可以在链的某一点find,它将停止search并使用它,否则一直持续到全局范围到达属于
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
。 由于variablesi
不能在function inner
find(既没有声明,也没有作为parameter passing),它继续search,直到i
的值最终在全局范围window.i
。
通过将其包装在一个外部函数中,可以明确定义一个helper函数,或者使用像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次的外部函数范围中find,每次都有正确绑定的值。 内部执行时不会使用window.i
的值。
更多细节可以在这里find
它包括在循环中创build闭包的常见错误,以及为什么我们需要closures和性能考虑。
我很惊讶没有人还build议使用forEach函数来更好地避免(重新)使用局部variables。 事实上,我没有使用for(var i ...)
在这个原因。
[0,2,3].forEach(function(i){ console.log('My value:', i); }); // My value: 0 // My value: 2 // My value: 3
//编辑为使用forEach而不是map。
具有ES6块级别范围确定的新function
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 }
code in question is replaced with let instead of var .
More on Let
Use ECMA-6 let keyword to bind the scope of a variable with block
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 }
I prefer to use forEach
function, which has its own closure with creating a pseudo range:
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 }
That looks uglier than ranges in other languages, but IMHO less monstrous than other solutions.
First of all, understand whats wrong with this code.
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 }
here when the funcs[]
array is being initialized, i
is being incremented, the funcs array is initialized and the size of func
array becomes 3, so i = 3,
. Now when the funcs[j]()
is called, it is again using the variable i
, which has already been incremented to 3.
Now to solve this, we have many options. Below are two of them.
- We can initialize
i
withlet
or initialize a new variableindex
withlet
and make it equal toi
. so when the call is being made,index
will be used and its scope will end after initialization. And for calling,index
will be initialized again.
。
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](); }
- Other Option can be to introduce a
tempFunc
which returns the actual function.
。
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](); }
so the reason your original example did not work is that all the closures you created in the loop referenced the same frame. in effect having 3 methods on one object with only a single 'i' variable. they all printed out the same value
You could use a declarative module for lists of data such as query-js (*). In these situations I personally find a declarative approach less surprising
var funcs = Query.range(0,3).each(function(i){ return function() { console.log("My value: " + i); }; });
You could then use your second loop and get the expected result or you could do
funcs.iterate(function(f){ f(); });
(*) I'm the author of query-js and therefor biased towards using it, so don't take my words as a recommendation for said library only for the declarative approach 🙂
Use closure structure, this would reduce your extra for loop. You can do it in single for loop.
var funcs = []; for (var i = 0; i < 3; i++) { (funcs[i] = function() { console.log("My value: " + i); })(i); }
Many solutions seem correct but they don't mention it's called Currying
which is a functional programming design pattern for situations like here. 3-10 times faster than bind depending on browser.
https://www.sitepoint.com/currying-in-functional-javascript/
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); } }
see performance gain in different browsers https://jsperf.com/bind-vs-curry
And yet another solution: instead of creating another loop, just bind the this
to the return function.
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 }
This is a problem often encountered with asynchronous code, the variable i
is mutable and at the time at which the function call is made the code using i
will be executed and i
will have mutated to it's last value.. thus meaning all functions created withing the loop will create a closure and i
will be equal to 3 (the upper bound + 1 of the for
loop.
A workaround to this, is to create a function that will hold the value of i
for each iteration and force a copy i
(as it is a primitive, think of it as a snapshot if it helps you).
You code doesn't work, because what it does is:
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?
Now the question is, what is the value of variable i
when the function is called? Because first loop is created with condition i < 3
, it stops immediately when the condition is false, so it is i = 3
.
You need to understand that, in time when your functions are created, none of their's code is executed, it is only saved for later. And so when they are called later, the interpreter executes them and asks "what is the current value of i"?
So, your goal is to first save value of i
to function and only after that save the function to funcs
. This could be done for example this way:
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 }
This way, each function will have it's own variable 'x' and we set this x
to value i
in each iteration.
This is only one of multiple ways how to solve this problem.
Let's take advantage of new Function. Thus "i" stopes to be a varibale of a closure and becomes just a part of the text.
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](); }
COUNTER BEING A PRIMITIVE
Lets define callback functions as follows:
// **************************** // COUNTER BEING A PRIMITIVE // **************************** function test1() { for (var i=0; i<2; i++) { setTimeout(function() { console.log(i); }); } } test1(); // 2 // 2
After timeout completes it will print 2 for both. This is because callback function accesses value based on lexical scope, where it was function was defined.
To pass and preserve the value while callback was defined, we can create a closure, to preserve value before the callback is invoked. 这可以如下完成:
function test2() { function sendRequest(i) { setTimeout(function() { console.log(i); }); } for (var i = 0; i < 2; i++) { sendRequest(i); } } test2(); // 1 // 2
Now whats special about this is "The primitives are passed by value and copied. Thus when the closure is defined, they keep the value from the previous loop."
COUNTER BEING AN OBJECT
Since closures have access to parent function variables via reference, this approach would differ from that for primitives.
// **************************** // 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
So, even if a closure is created for the variable being passed as an object, value of loop index will not be preserved. This is to show that values on object are not copied whereas they are accessed via reference.
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