<!doctype html>
<html>
<head>
<meta charset = "utf-8" />
</head>
<div id = "target"></div>
<script>
var div = document.getElementById("target");
for(var i = 0; i < 8; i++){
var p = document.createElement("p");
var b = document.createElement("button");
b.innerText = "button" + i;
div.appendChild(b);
div.appendChild(p);
b.onclick = function(){
p.innerText = i;
}
}
</script>
</body>
</html>
上面代码原意是希望将生成的button元素,绑定相对应的p元素,但是运行后发现所有的button元素都绑定了最后一个p元素。
为什么?
这是因为onclick事件绑定的函数的词法环境(作用域)都是for循环花括号区域,区域中有p,b,i三个变量。
而b.onclick = function(){}和这个作用域形成了闭包,函数中使用该作用域的p,i变量,造成所有绑定事件共享这两个变量。
在页面加载后,所有button 未被点击前,for循环已经结束。作用域变量变为i = 8, p => 最后一个p。
一触发onclick事件,调用function(){p.innerText = i;},就变成了最后一个p元素显示文本8。
这十分像java的继承,触发一次onclick就是一次“继承”,然后共用父类的成员变量, p,b是“protected类型”。
闭包的参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
所以,在公共的作用域for循环没有结束前立刻保存每一次循环的p和i是十分必要的。
考虑用参数传递的方式将每次的p和q传递到function(){p.innerText = i;}中,则有:
function test(p,i){p.innerText = i;}
立刻触发事件传递参数: test(p,i);
又onclick需要绑定一个函数,但是b.onclick = function(){}的写法在触发时不能同时绑定。故让test函数内部返回一个操作函数,test函数作为直接的绑定。
b.onclick = function test (p,i){
return function(){
p.innerText = i;
}
}
test (p,i);
这样就间接绑定了操作,也传递了参数。
使用匿名函数可以将代码合并:
b.onclick = (function(p,i){
return function(){
p.innerText = i;
}
})(p,i);
以上代码可以实现效果。
除了闭包外,还可以使用let来处理。
let定义函数变量有各自的块作用域,可以保证onclick事件的词法环境中let定义的变量的不同的。
代码可改为:
var div = document.getElementById("target");
for(let i = 0; i < 8; i++){
let p = document.createElement("p");
var b = document.createElement("button");
b.innerText = "button" + i;
div.appendChild(b);
div.appendChild(p);
b.onclick = function(){
p.innerText = i;
}
}