不知道大家有没有发现一个问题,当在一个 for 循环中直接打印 i 时候,i 的值并不是我们想的那样。
html代码
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
先获取到页面中的li
var lis=document.querySelectorAll('ul li');
然后来个 for 循环
for(var i=0;i<lis.length;i++){
lis[i].addEventListener('click',function(){
console.log(i);//都是3
},false);
}
我们会发现,当点击 li 时,输出的 i 的值不是我们想的0 1 2, 而是3,可能我们会认为i<lis.length=3
,咋也不可能等于3鸭!
这里涉及到同步&异步的概念。
事件的执行是异步的,异步代码会被置于一个特殊的等待队列中,它只会在浏览器“有空闲时间”的时候才会执行。
说人话就是console.log(i)
被浏览器放在一边等待,(因为只绑定click事件,而不运行其中代码)等浏览器“有空”的时候,就是循环绑定完事件后,这时 i 已经自增到3了,所以打印出来都是3。(i=2时人家还在循环,等于3才跳出循环)
那么如何解决这个问题呢
法一:将var改成let
for(let i=0;i<lis.length;i++){
lis[i].addEventListener('click',function(){
console.log(i);//0 1 2
},false);
}
这样就解决了,那么为什么呢?
我们先来说说let,ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
也就是说var声明的是全局变量,而let声明的是局部变量。
var声明只声明了一个共享的全局变量 i ,而let声明多个 i ,i = 0,i = 1…
每个 i 只对当前的区域有效。
法二:封装成一个匿名立即执行函数 并定义一个局部变量j
for(var i=0;i<lis.length;i++){
(function(j){
lis[i].addEventListener('click',function(){
console.log(j);
},false);
})(i);//将i作为参数传给j
}
这里的关键也是局部变量,封装成函数相当于另外开辟出一块区域,然后在里边定义一个局部变量j。
法三:保存当前对象的索引
for(let i=0;i<lis.length;i++){
lis[i].index=i;
lis[i].addEventListener('click',function(){
console.log(this.index);//0 1 2
},false);
}
如果前面两个不是很好理解的话可以看下法三,和法二相似,将 i 赋值给lis[ i ].index,保存当前对象的索引,然后输出this.index