JavaScript for循环闭包问题解决方法
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。
html:
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
js:
var item = document.getElementsByClassName("item");//获取dom类数组
当我们想为 每一个item 添加事件时可能会这样写(打印元素的索引):
for (var i = 0; i < item.length; i++) {
item[i].addEventListener("click", function (e) {
console.log(i);//333
}, false)
}
最后会发现无论点击哪个item 打印的都是3。关于for 循环,需要了解 for 循环的执行顺序是 先声明var i = 0
,然后i++
,然后 判断 i < item.length
是否为true,如果为true就执行循环体。由于var声明的变量没有块级作用域,在{}
声明 i = 0 相当于在外部(全局)声明了 一个 var i = 0。
当点击事件的回调函数执行时,由于函数当前作用域内没有i,会去作用域链下一栈找,而此时下一栈是GO(全局预编译),也就是最外层的环境.而外部的的i会被最后一次循环后赋值为var i = 3
,所以无论点击哪个 打印结果都是3。
使用立即执行函数的解决方法:
for (var i = 0; i < item.length; i++) {
(function (i) {
item[i].addEventListener("click", function (e) {
console.log(i);//0 1 2
}, false)
}(i))
}
使用立即执行函数后,for循环时声明的i被传入到立即函数中, 此时立即执行函数的函数体里用的 i 是立即执行函数的形参 i,不是for循环时声明的i。当点击事件的函数执行时,他找到的i 也是立即执行函数形参上的i。所以可以正确打印每个元素的索引。
使用es6 let的解决方案:
for (let i = 0; i < item.length; i++) {
item[i].addEventListener("click", function (e) {
console.log(i);// 0 1 2
}, false)
}
es6 用let命令实现了块级作用域,也就是在 {}
中声明的变量在外部是读不到的,i 只在本轮循环中有效,所以 当点击事件的函数执行时,他在当前作用域找不到i,会去作用域链的下一栈去找,下一栈的作用域也就是用let命令在{}
中形成的块级作用域,也就能拿到本轮循环时的i,就拿到了正确的元素索引。
你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。--参考自:https://es6.ruanyifeng.com/#docs/let
for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
版权声明:
作者:东明兄
链接:https://blog.crazyming.com/technology-sharing/1807/
来源:CrazyMing
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论