记得刚刚接触javascript时,自己对javascript的闭包也很迷惑。有一次,一位同事遇到了下面的问题,很多同学一定也遇到过类似的问题。
我的blog原文:点击打开链接
问题描述
我们要给四个有编号的按钮绑定点击事件。当点击某个按钮时,弹出此按钮的编号。
<button>0</button> <button>1</button> <button>2</button> <button>3</button>
var button = document.getElementsByTagName("button"); var addEventListener = function(nodes){ var i; for(i = 0; i < nodes.length; i++){ nodes[i].onclick = function(e){ alert(i); } } } addEventListener(button);
大家可以到这里运行它.
结果是什么?结果是,无论我们点击哪个按钮,都会弹出按钮的个数,是不是似曾相识?为什么呢?
一个闭包的例子
function foo(){ var a = 10; function bar(){ a *= 2; return a; } return bar; } var baz = foo(); alert(baz()); //20 alert(baz()); //40 alert(baz()); //80
大家可以到这里运行它.
在javascript中,只有函数具有作用域。也就是说,在一个函数内部声明的变量在函数外部无法访问。
在上述代码中,所返回的对bar函数的引用被赋给变量baz.这个函数现在是在foo外部被调用,但它依然能够访问a。这是因为javascript中的作用域是词法性的。函数是运行在定义它们的作用域中(本例中是foo内部的作用域),而不是运行在调用它们的作用域中。只要bar被定义在foo中,它就能访问在foo中定义的所有变量,即使foo的执行已经结束。
上面就是一个闭包的例子。我想,我不能只用一句话解释什么是闭包,或者定义什么是闭包,只能通过这个例子了。
再看第一个例子
有了上面对闭包的解释,再看一下那些按钮当被点击的时候为什么总是弹出按钮的个数呢?
我们可以这样想:for循环中,我们只是为所有按钮绑定了点击事件,当它们被点击时会弹出i的当前值。但是,当循环结束的时候,i的值已经是按钮的个数了。这时,当我们再点击按钮的时候,程序要弹出i的值,也当然就是按钮的个数了,就像上面闭包的那个例子一样。
一种改进方案
var button = document.getElementsByTagName("button"); // 改良后 var addEventListener = function(nodes){ var helper = function(i){ return function(e){ alert(i); }; }; var i; for(i = 0; i < nodes.length; i++){ nodes[i].onclick = helper(i); } } addEventListener(button);
大家可以到这里运行它.
另一种改进方案
var button = document.getElementsByTagName("button"); // 也可以这样 var addEventListener = function(nodes){ var i; for(i = 0; i < nodes.length; i++){ nodes[i].onclick = (function(anotherI){ return function(){ alert(anotherI); }; })(i); } } addEventListener(button);
大家可以到这里运行它.
但要避免在循环中创建函数,它可能只会带来无谓的计算,还会引起混淆。所以,最好还是采用第一种改进方案,虽然它比较费事。
参考:《javascript语言精粹》p36-39,《javascript设计模式》p29-30