js 中 for 循环的语法糖解开是什么样的?为什么 let 和 var 导致输出结果不同? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Newyorkcity
V2EX    问与答

js 中 for 循环的语法糖解开是什么样的?为什么 let 和 var 导致输出结果不同?

  •  2
     
  •   Newyorkcity 2020-12-27 23:18:16 +08:00 3117 次点击
    这是一个创建于 1824 天前的主题,其中的信息可能已经有所发展或是发生改变。
    function foo() { var funcs = []; for (var i = 0; i < 3; i++) { //此处使用 var i = 0 funcs[i] = function () { console.log("funcs - " + i); }; } for (var j = 0; j < 3; j++) { funcs[j](); } } foo(); 

    输出结果:
    funcs - 3
    funcs - 3
    funcs - 3

    function foo() { var funcs = []; for (let i = 0; i < 3; i++) { //此处使用 let i = 0 funcs[i] = function () { console.log("funcs - " + i); }; } for (var j = 0; j < 3; j++) { funcs[j](); } } foo(); 

    输出结果:
    funcs - 0
    funcs - 1
    funcs - 2

    我知道 let 的作用域被 {} 限制,而 var 的作用域等于其所在的函数的函数体,但就从这个知识点我不能解释上述情况出现的原因。我觉得其中我对 for 语法糖背后的实际代码不了解也是重要的原因,所以来请教下。感谢。

    30 条回复    2020-12-30 00:12:04 +08:00
    chashao
        1
    chashao  
       2020-12-27 23:28:21 +08:00 via Android
    应该是去了解一下闭包的原理吧,闭包保存的是上下文,所以上下文里面的变量 var 只被创建一次,但是 let 每次循环都会重新创建新的变量(前面都是我乱写
    hyuka
        2
    hyuka  
       2020-12-27 23:32:39 +08:00 via iPhone
    不就是因为你说的吗,上面的实现在输出时 i 就为 3 了;再 funcs 调用就输出了 3,接下来的循环 i 没变,一直为 3
    anguiao
        3
    anguiao  
       2020-12-28 00:04:42 +08:00 via Android
    上面的 funcs[0/1/2]指向的都是同一个“i”,循环结束后就变成 3 了。后面打印的时候,因为 i 已经是 3 了,所以输出全都是 3 。
    Newyorkcity
        4
    Newyorkcity  
    OP
       2020-12-28 00:36:46 +08:00
    @hyuka
    @anguiao

    所以怎么解释 let 的情况不一样?
    autoxbc
        5
    autoxbc  
       2020-12-28 00:44:40 +08:00 via iPhone
    for 设计的时候还没有 let,等 let 出现后 for 有了新的语义,即每个循环节互相隔离,let 初始化的变量在每个循环节有个副本,取值只对当前循环节有效

    这样的设定是强行兼容 let,尤其是 let 并不在块语句中这个现实,还有 let 自增后值可以跨循环节保持也反直觉

    我觉得 for 是旧时代的糟粕,函数式和迭代器才是未来,应该多用 forEach 和 for ...of
    anguiao
        6
    anguiao  
       2020-12-28 00:45:29 +08:00 via Android
    @Newyorkcity 因为每次循环其实都创建了一个新的“i”,所以绑定的就是每次循环时 i 的值。
    Arthur2e5
        7
    Arthur2e5  
       2020-12-28 06:33:40 +08:00   1
    > @autoxbc 这样的设定是强行兼容 let,尤其是 let 并不在块语句中这个现实

    这完全(语气助词)正常,早就有语言这么做。这个 for 的行为和以下的 C99 代码行为是一致的:

    ```C
    for (int i = 0; i < 3; i++) /* blah */;
    ```

    觉得这个东西很奇怪只能说是 JS 程序员见怪不怪。一个 var 影响全场才叫怪。
    Elethom
        8
    Elethom  
       2020-12-28 06:51:14 +08:00 via iPhone
    @Arthur2e5
    「强行兼容 let 」这句笑傻了。
    rodrick
        9
    rodrick  
       2020-12-28 08:18:23 +08:00   2
    看一下这个 https://es6.ruanyifeng.com/#docs/let
    其实明白两点就好了:
    1. var 的循环公用一个 i
    2. let 的循环每次都是新的 i
    3. Javascript 引擎内部会记住上一轮循环的值,初始化本轮的变量 i 时,就在上一轮循环的基础上进行计算。

    至于作用域问题,for 那块是一个作用域,下面的{}又是一个子作用域,互不干扰:
    ```
    for (let i = 0; i < 3; i++) {
    let i = 'abc';
    console.log(i);
    }
    ```
    这个打印的都是 "abc" 也不会报 i 已经声明的错
    autoxbc
        10
    autoxbc  
       2020-12-28 08:32:50 +08:00 via iPhone
    @Arthur2e5 没错,就是从 C 借来的糟粕,你读读现代 JS,根本没有人用 for,一股 C 味的 JS 最可怕了
    yeship
        11
    yeship  
       2020-12-28 08:36:24 +08:00
    由于 Javascript 的事件循环,function 回调会在遍历结束后才执行。因为在第一个遍历中遍历 i 是通过 var 关键字声明的,所以这个值是全局作用域下的。在遍历过程中,我们通过一元操作符 ++ 来每次递增 i 的值。当 function 回调执行的时候,i 的值等于 3 。

    在第二个遍历中,遍历 i 是通过 let 关键字声明的:通过 let 和 const 关键字声明的变量是拥有块级作用域(指的是任何在 {} 中的内容)。在每次的遍历过程中,i 都有一个新值,并且每个值都在循环内的作用域中。
    Seanfuck
        12
    Seanfuck  
       2020-12-28 08:42:44 +08:00 via iPhone
    funcs[i]=function(i){……} 试试
    hubqin
        13
    hubqin  
       2020-12-28 08:46:08 +08:00 via Android   1
    了解下 js 的变量提升,所有 var 声明都会被提取到文件或函数开头,上面用 var 的循环,相当于在循环前面连续声明了 3 次 i,var i=1;var i=2;var i=3; i 是个全局变量,最终内存地址中 i 指向的值是 3 。而 let 声明的变量是块级作用域的,每个 {} 都是一个块级作用域,每次 for 也是一个块级作用域,所以 for... let...循环,声明了三次 i,值分别为 1 2 3
    SxqSachin
        14
    SxqSachin  
       2020-12-28 09:14:47 +08:00   1
    楼上这么多大佬给出的回答居然没有重复的
    我在知乎上看到过一篇文章,感觉解释的比较清楚
    [我用了两个月的时间才理解 let]( https://zhuanlan.zhihu.com/p/28140450)
    [Runtime Semantics: LabelledEvaluation]( http://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation)
    yaphets666
        15
    yaphets666  
       2020-12-28 09:18:59 +08:00
    @autoxbc
    有时候不得不使用 for 循环
    比如在循环中使用 await 的时候
    autoxbc
        16
    autoxbc  
       2020-12-28 09:40:38 +08:00
    @yaphets666 #15 for ... of 就是现代的 for,可以在其中使用 await 达成目的
    GDC
        17
    GDC  
       2020-12-28 09:42:10 +08:00
    @yaphets666 for...of 和 for...in 都可以 await
    yaphets666
        18
    yaphets666  
       2020-12-28 09:43:58 +08:00
    @autoxbc
    @GDC
    我去试试.我记得以前用的时候没有达到预期的阻塞效果
    SmallTeddy
        19
    SmallTeddy  
       
    我建议楼主看一下《你不知道的 Javascript 》这本书的上卷所讲的作用域部分的知识
    wuzhanggui
        20
    wuzhanggui  
       2020-12-28 10:13:39 +08:00
    for 循环只是一个代码块(内部分为了多个),var 变量不区分代码块,let 要区分,同一个代码块中生命多次 var a 相当于给第一次 var 的 a 重新赋值而已,所以 for (var i = 0; i < 3; i++)相当于 var i = 0;for (i = 0; i < 3; i++),而 let 就不同了,相同代码块中声明名字相同的 let 变量是不允许的,所以每个代码块中的同名称变量不同。所以 for 循环中那个函数访问的也不同。
    3wdddd
        21
    3wdddd  
       2020-12-28 10:31:05 +08:00
    @yaphets666 这样很慢,应该用 promise.all
    3wdddd
        22
    3wdddd  
       2020-12-28 10:32:24 +08:00
    i 在整个循环内有多个副本,var 只有函数顶部一个
    zankard
        23
    zankard  
       2020-12-28 10:44:07 +08:00 via iPhone
    可以去了解下 js 的 Lexical Environment 。let 是 block scope,每一次 for 迭代都会生成一个新的 Lexical Environment,使得每个迭代使用的 let 变量都不一样;而 var 是 function scope,使用的都是一个变量。
    AmoreLee
        24
    AmoreLee  
       2020-12-28 11:00:03 +08:00
    kx5d62Jn1J9MjoXP
        25
    kx5d62Jn1J9MjoXP  
       2020-12-28 11:10:20 +08:00
    var 不支持 local scope, for 循环定义的 var 是定义在函数体中的
    zckevin
        26
    zckevin  
       2020-12-28 13:03:19 +08:00
    // {
    // let/const x = i;
    // temp_x = x;
    // first = 1;
    // undefined;
    // outer: for (;;) {
    // let/const x = temp_x;
    // {{ if (first == 1) {
    // first = 0;
    // } else {
    // next;
    // }
    // flag = 1;
    // if (!cond) break;
    // }}
    // labels: for (; flag == 1; flag = 0, temp_x = x) {
    // body
    // }
    // {{ if (flag == 1) // Body used break.
    // break;
    // }}
    // }
    // }
    zckevin
        27
    zckevin  
       2020-12-28 15:20:00 +08:00
    《 let/const for loop 的 v8 实现》
    https://zhuanlan.zhihu.com/p/340068236
    Arthur2e5
        28
    Arthur2e5  
       2020-12-29 17:15:03 +08:00   1
    @autoxbc 没有人用 for ? for 的三段形式是对过程式程序循环的最基本表达,有设置循环变量、循环条件、向下一个循环准备的内容。

    最基本的 (i = 0; i < ...; i++) 当然可以用 for of 的 iterator 表达(说 for in 的建议吊起来打),但并不是所有循环都是只有一种 iterable 提供的循环方法的。即使是最基本的 Array 都有倒过来迭代的理由,换到地铁图的图论玩意儿那当然更有。

    是,你可以自己写别的的 iterator 把 [Symbol.iterator] 换了。但是你自己看看那玩意是不是就等于把那三段东西又写了一遍,还得复制粘贴空白模板代码?
    autoxbc
        29
    autoxbc  
       2020-12-29 20:51:24 +08:00
    @Arthur2e5 #28
    for 里的循环变量显示 for 的设计者认为下标访问是对迭代的正确抽象,事实是对迭代的正确抽象是迭代器。迭代器可以设计成正向迭代,自然也可以添加反向迭代方法,这并没有超出设计范围

    ES6 对所有集合体对象部署了迭代器,没有对任何集合体添加新的下标访问接口,原有的基于下标的迭代过程实现为基于迭代器接口,这就是工业界给出的答案
    Arthur2e5
        30
    Arthur2e5  
       2020-12-30 00:12:04 +08:00
    > @autoxbc 下标访问是对迭代的正确抽象

    谁说循环变量是下标了?

    > 正确抽象是迭代器

    你再好好想想,迭代器储存了什么状态?迭代器储存的状态是不是一种循环变量?
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     913 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 22:28 PVG 06:28 LAX 14:28 JFK 17:28
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86