<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<style>
#list {
width: 60%;
height: 40vh;
display: flex;
padding: 0;
flex-direction: row;
margin: 10vh auto;
border: 1px solid red;
list-style: none;
}
#list > li {
flex: 1;
border: 1px solid yellow;
}
</style>
<body>
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var children = document.getElementById("list").children;
for (var index = 0; index < children.length; index++) {
children[index].onclick = function(){
console.log(index) // -> 3, 3, 3 统一打印出最后一位
}
}
</script>
</body>
</html>
在点击相对应的DOM元素时,按正常逻辑来说应该会打印对应的index值,但是实际情况的都为3,也就是数组的长度最后一位,这是为什么呢?
我的理解是:click事件只是一个指针,函数并没有执行( 值任然是动态的 ),而index的 重复赋值 直接覆盖了之前的值,换句话说回调函数里的index指向是同一个变量!!
我为什么会得出这样的结论呢? 其实换位思考下JS会怎么解析这段代码就容易理解了:
JS没有块级作用域一直被诟病,结合变量声明置顶( for循环的index直接成为全局变量,而不是局部变量 ),而我们可以用一个异步函数来模拟click事件( 你可以称它为未来事件或者待执行事件 ),由此我们可以间接模拟出函数的解析步骤....
var index; // for循环中的index值
index = 0;
setTimeout(function(){ // 模拟click点击事件
console.log(index) // -> 1
}, 1000)
++index; // 模拟for循环
所述综上由此可得出解决方案
- 当前值不可变( 既立即执行 )
children[index].onclick = function (i) { return function () { console.log(i) // -> 0, 1, 2 } }(index) // 保存当前索引值
立即执行函数配合闭包保存当前索引值,你也可把它放在外面会更加优雅,不过思路是一样
-
解除引用值( 既单独为每个i取唯一标识符 )
for (let index = 0; index < children.length; index++) { // var -> let children[index].onclick = function () { console.log(index) // -> 0, 1, 2 } }
最简单的莫过于直接用ES6的 let 局部变量 替换var, 还有一种解决方案
for (var index = 0; index < children.length; index++) { children[index].i = index // 给当前dom元素添加一个元素保存索引 children[index].onclick = function(){ console.log(this.i) // -> 0, 1, 2 } }
一点拙见,如有不足,望相互探讨!!