美文网首页
var 产生的闭包问题

var 产生的闭包问题

作者: BA_凌晨四点 | 来源:发表于2020-04-29 18:12 被阅读0次

    曾经见过这样的一道面试题:

     <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
    </ul>
    

    鼠标点击相应的<li>,输出那个<li>的索引号。
    当时年少无知,这不简单吗,直接注册事件不就完事了吗?

    var lis = document.querySelectorAll('li');
    for (var i = 0; i < lis.length; i++) {
        lis[i].addEventListener('click', function () {
            console.log(i);
        })
    }
    

    结果。。

    图片.png
    为何4个4。?
    后来研究了下,这是因为JavaScript的缺陷导致,也就是变量声明提升导致的闭包问题
    console.log(i);  //undefined
    var i = 123;
    

    看到没有,我没有定义 i 的时候,直接打印不会报错。这是因为下面用 var 定义了 i,飘到上面去了。。这称之为函数声明提升,提升到作用域的顶端。

    lis[i]上注册的那个click匿名函数形成了封闭的作用域(闭包)。当 for 循环结束时候,这个闭包的才打开,世界早已经发生了翻天覆地的变化了。此时的 i 不再是当年的 i 了。此时的 i 已经是4了。

    解铃还须系铃人,闭包问题当然可以用闭包的方法去解决啦。
    利用立即执行函数的方法:

    for (var i = 0; i < lis.length; i++) {
        (function (j) {
            lis[j].addEventListener('click', function () {
                console.log(j);
            })
        }(i))  //将每次循环的 i 传给 j ,保存起来。当每次执行 click 的时候,就能打印相应的索引了
    }
    

    当然也有其他的写法,但是原理是一样的

    for (var i = 0; i < lis.length; i++) {
        temp(i);  //每次循环,及时把 i 传过去
    }
    function temp(j) {
        lis[j].addEventListener('click', function () {
            console.log(j);
        })
    }
    

    但是利用这种原理去解决,都是要写一堆很恶心的代码。。。就为了这个小问题。

    我们来分析一下。

    图片.png
    所以,ES6提出了一种定义变量的方法就是 let 。它解决了原来js几个缺陷。其中就是,函数声明,不会提升,可以这么理解。
    console.log(i);  //报错。
    let i = 123;
    

    在循环中,用 let 声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域。也就是每次进入循环,用的都是一个新的变量 i
    这是因为 let 会产生一个块级作用域 {},
    所以主题中说的问题可以这样写:

    for (let i = 0; i < lis.length; i++) {  //这里一共分别产生了4个作用域,每个域绑定1个i  
        lis[i].addEventListener('click', function () {   //这里的i都是全新的
            console.log(i);
        })
    }
    

    在循环中用 let 声明的循环变量,在循环结束的时候回销毁。
    因此,在循环结束的时候:
    console.log(i); //error: i is not defined

    其实底层实现上,let 声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区(TDZ),则会报错:“Cannot access 'a' before initialization”。
    只有当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。


    图片.png

    因此,将用 let 声明的变量理解成不会提升,完全不会出问题。
    而且,var 还导致了其他问题,比如:

    • 全局变量挂载到全局:全局对象成员的污染问题。
    • 重复声明变量, 导致覆盖。

    所以,在声明变量的时候,一般不用var去声明,尽量使用ES6的let或者const

    相关文章

      网友评论

          本文标题:var 产生的闭包问题

          本文链接:https://www.haomeiwen.com/subject/rzsnwhtx.html