V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
KURANADO
V2EX  ›  JavaScript

JavaScript let 关键字问题求解

  •  
  •   KURANADO · 2018-03-18 21:40:04 +08:00 · 4900 次点击
    这是一个创建于 2440 天前的主题,其中的信息可能已经有所发展或是发生改变。

    写了这样一段代码:

    mark

    这段代码的目的是使用循环为多个元素帮点点击事件,但是测试发现并没有按照预想的那样正确的为元素绑定事件。

    后来发现回调函数中的变量 i 的颜色和其他 i 的颜色不同,鼠标悬浮在该 i 上,IDEA 提示如下:

    Mutable variable is accessible from closure.

    我知道这是循环和异步调用的经典问题,可以通过闭包来解决,修改代码如下:

    function clickImageIcon(msgArr, options) {
        for (var i = 0; i < msgArr.length; i ++) {
            (function(index) {
                $('.file-wrapper:eq(' + index + ')').bind('click', function () {
                    recognitionContent(msgArr[index]);
                    $('#myModal').modal(options);
                });
            })(i);
        }
    }
    

    但是在 Stack Overflow 上有人提出用 let 代替 var 也可以解决这个问题,代码如下:

    function clickImageIcon(msgArr, options) {
        for (let i = 0; i < msgArr.length; i ++) {
            $('.file-wrapper:eq(' + i + ')').bind('click', function () {
                recognitionContent(msgArr[i]);
                $('#myModal').modal(options);
            });
        }
    }
    

    我想知道为什么换成 let 也可以解决问题,求各位大神告知!

    18 条回复    2018-03-21 15:07:11 +08:00
    tortorse
        1
    tortorse  
       2018-03-18 21:43:40 +08:00
    作用域、闭包、变量提升了解一下
    murmur
        2
    murmur  
       2018-03-18 21:47:49 +08:00
    给你个解决方法 当你搞不懂闭包和作用域的时候 就在循环的时候用 bind 把 i 绑死 这样循环到几他调用的时候就是几
    或者更无耻一点 写个 data-index 添加到你的元素上 click 事件的时候直接读 data-index 就可以了
    这两个方法省事还不纠结作用域 第一个 es5 就可以 第二个甚至连 es5 都不要求
    DOLLOR
        3
    DOLLOR  
       2018-03-18 21:52:53 +08:00   ❤️ 1
    var 是函数级作用域,let 是块级作用域。
    而 C 语言、JAVA 语言跟 let 关键字一样,都是块级作用域。
    polythene
        4
    polythene  
       2018-03-18 21:56:50 +08:00
    let 语句使得在每一次循环的都生成一个 i
    murmur
        5
    murmur  
       2018-03-18 22:01:06 +08:00
    @polythene babel 转义不是只处理变量名冲突的时候才会把 i 换成_i 么
    那这个在 babel 转义后还有用么
    drackzy
        6
    drackzy  
       2018-03-18 22:04:18 +08:00
    你可以看看《 You Don't know JS 》这本书闭包那章这个问题讲的很清楚
    lsvih
        7
    lsvih  
       2018-03-18 22:12:18 +08:00
    ```
    for (var i = 0; i < msgArr.length; i ++)
    ```

    其实相当于

    ```
    var i;

    for (i = 0; i < msgArr.length; i ++)
    ```

    你 bind 的 function 拿到的是 i 的引用,也就是循环最后 i 的值。

    let 从语法上说是块级作用域,在每次 for 循环中 bind 中 function 拿到的其实都是不同的 i 的引用。实际实施起来(比如用 babel 转一下)和你的立即执行函数的写法应该是差不多的,相当于传的是实参
    Mojy
        8
    Mojy  
       2018-03-18 22:25:53 +08:00
    顺便学习了,let 是块级的,var 是函数级的。也就是说 let 在整个 for 循环里都是有效的,但 var 由于闭包的原因,在回掉函数里就会失效。
    不知道理解的是否正确
    murmur
        9
    murmur  
       2018-03-18 22:32:12 +08:00
    @Mojy 如果我语文没问题,你是不是理解反了还是把例子看反了
    var 最开始的例子是因为 i 实际上提前到循环外面来了 这样最后事件回调里引用就是 i 最后一个值
    let 的块级。。算了找个语文好的大佬给你解释下吧
    we2ex
        10
    we2ex  
       2018-03-18 23:02:28 +08:00 via Android
    完全抛弃 var 就好了,从 ES6 就应该全部改用 let / const 了
    coolcoffee
        11
    coolcoffee  
       2018-03-18 23:09:12 +08:00
    看一下 babel 转换后的代码就知道 let 的工作原理了。

    ``` javascript

    "use strict";

    function clickImageIcon(msgArr, options) {
    var _loop = function _loop(i) {
    $(".file-wrapper:eq(" + i + ")").bind("click", function() {
    recognitionContent(msgArr[i]);
    $("#myModal").modal(options);
    });
    };

    for (var i = 0; i < msgArr.length; i++) {
    _loop(i);
    }
    }


    ```
    MinonHeart
        12
    MinonHeart  
       2018-03-18 23:29:01 +08:00 via iPhone
    jQuery 方案
    .bind(‘ click ’, i, function (e) {
    recongnitionContent(msgArr[e.data])
    ...
    })
    sunjourney
        13
    sunjourney  
       2018-03-19 01:19:51 +08:00
    我的天,你这代码写的太恶心了吧

    ```
    function clickImageIcon(msgArr, options) {
    const $modal = $('#myModal')
    $('.file-wrapper').each(($el, index) => { // 如果你会事件代理,这里还可以继续优化
    $el.on('click', function() {
    recognitionContent(msgArr[index]); // 不知道这是想干嘛,尽量不要用有副作用的函数
    $modal.modal(options)
    })
    })

    clickImageIcon = null
    }
    ```
    hoythan
        14
    hoythan  
       2018-03-19 02:29:25 +08:00
    JQ 事件绑定的第二个参数不是子元素吗?为啥不用。。。
    viewsing
        15
    viewsing  
       2018-03-19 08:35:59 +08:00 via Android
    let 在 for 循环的声明中有有特殊的效果,每一轮都会创建一个新的块级作用域
    heyOhayo
        16
    heyOhayo  
       2018-03-19 17:18:18 +08:00
    一看就是后端写的代码,前端不会问这么幼齿的问题~
    KURANADO
        17
    KURANADO  
    OP
       2018-03-20 16:01:22 +08:00
    感谢各位大佬的回答!
    pheyer
        18
    pheyer  
       2018-03-21 15:07:11 +08:00
    @lsvih 你这个等效是不是有点不对,把 var 换成 let,i 变量的声明仍然在 for 循环之前的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3515 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 10:25 · PVG 18:25 · LAX 02:25 · JFK 05:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.