JavaScript for循环闭包问题解决方法

Daming 2018-01-08
0条评论 1,734 次浏览
Daming 2018-01-080条评论 1,734 次浏览

闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。

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循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

4+

发表评论

电子邮件地址不会被公开。