闭包经典使用场景之一:通过循环给页面上多个dom节点绑定事件。
题目:现在有个HTML片段,要求编写代码,点击编号为几的链接就弹出其编号
<ul>
<li>编号1,点击我请弹出1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
一般不知道这个题目用闭包的话,会写出下面的代码:
for(var i = 0; i < list.length; i++) {
list[i].addEventListener('click', function(){
alert(i)
}, true)
}
或者
for(var i = 0; i < list.length; i++) {
list[i].onclick = function() {
alert(i)
}
}
结果是点击每一项都弹出5 。原因是onclick事件是异步触发的,当事件被触发时,for循环早已结束,此时变量i的值已经是5!故当onclick事件顺着作用域链从内向外查找变量i时,找到的值总是5。
例如:https://jsbin.com/hibuxirewu/edit?html,js,output
若使用闭包改写:https://jsbin.com/balofiwige/edit?html,js,output
var list = document.getElementsByTagName('li')
for(var i = 0; i < list.length; i++) {
list[i].addEventListener('click', function(i){
return function() {
alert(i+1)
console.log(i+1)
}
}(i), true)
}
在闭包作用下,定义事件函数的时候,每次循环的i值都封闭起来,这样在函数执行时,会查找定义时的作用域链,这个作用域链里的i值是在每次循环中都被保留的,因此点击不同的li会alert出不同编号。
下面总结onclick事件的6种解决办法:
1、加一层闭包,返回一个函数作为响应事件
https://jsbin.com/qaziqecacu/edit?html,js,output
var list = document.getElementsByTagName('li')
for(var i = 0; i < list.length; i++) {
list[i].onclick = function(j) {
return function() { // 返回一个函数
alert(j+1)
console.log(j+1)
}
}(i)
}
2、用Function实现,实际上每产生一个函数实例就会产生一个闭包
https://jsbin.com/wumeraxive/edit?html,js,output
var list = document.getElementsByTagName('li')
for(var i = 0; i < list.length; i++) {
list[i].onclick = new Function("alert(" + i +")")
// new一次就产生一个函数实例
}
3、用Function实现,注意与法2的区别
https://jsbin.com/dowoxiqaji/edit?html,js,output
var list = document.getElementsByTagName('li')
for(var i = 0; i < list.length; i++) {
list[i].onclick = Function("alert(" + i +")")
}
4、加一层闭包,i以局部变量形式传递给内层函数
https://jsbin.com/zipakuviki/edit?html,js,output
var list = document.getElementsByTagName('li')
for(var i = 0; i < list.length; i++) {
(function() {
var temp = i // 调用时局部变量
list[i].onclick = function() {
alert(temp+1)
console.log(temp+1)
}
})()
}
5、加一层闭包,i以函数参数形式传递给内层函数
https://jsbin.com/qeyequqepo/edit?html,js,output
var list = document.getElementsByTagName('li')
for(var i = 0; i < list.length; i++) {
(function(j) {
list[i].onclick = function() {
alert(j+1)
console.log(j+1)
}
})(i)
}
6、将变量i保存在匿名函数自身
https://jsbin.com/wezayikunu/edit?html,js,output
var list = document.getElementsByTagName('li')
for(var i = 0; i < list.length; i++) {
(list[i].onclick = function() {
alert(arguments.callee.i + 1)
console.log(arguments.callee.i + 1)
}).i = i
}
7、将变量保存在每个元素对象li上
https://jsbin.com/ladixutowe/edit?html,js,output
var list = document.getElementsByTagName('li')
for(var i = 0; i < list.length; i++) {
list[i].i = i
list[i].onclick = function() {
alert(this.i + 1)
console.log(this.i + 1)
}
}
8. 先在循环之外创建一个辅助函数让其再返回一个绑定了当前i值的函数
https://jsbin.com/widetuhali/edit?html,js,output
var list = document.getElementsByTagName('li')
var helper = function(i) {
return function() {
alert(i+1)
console.log(i+1)
}
}
for(var i = 0; i < list.length; i++) {
list[i].onclick = helper(i)
}