最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 你真的完全理解for中的let了吗,连Babel都不对!细说for的黑魔法

    正文概述 掘金(Neoficialaj)   2021-04-06   603

    你的第一感觉以下代码会输出什么呢?

    for(let i = 0 , _s = setTimeout(() => console.log('for1', i)); 
        setTimeout(() => console.log('for2', i)), i < 5; 
        setTimeout(() => console.log('for3', i))
        ) {
        setTimeout(() => console.log('i', i))
        i++
    }
    

    实际运行结果如下:

    for1 0
    for2 1
    i 1
    for3 2
    for2 2
    i 2
    for3 3
    for2 3
    i 3
    for3 4
    for2 4
    i 4
    for3 5
    for2 5
    i 5
    for3 5
    for2 5
    

    如果你预计for1、for2、for3都会输出5的话,恭喜你,Babel也是这么想的:

    // 上述JavaScript代码的babel编译结果
    "use strict";
    
    var _loop = function _loop(_i, _s) {
      setTimeout(function () {
        return console.log('i', _i);
      });
      _i++;
      i = _i;
    };
    
    for (var i = 0, _s = setTimeout(function () {
      return console.log('for1', i);
    }); setTimeout(function () {
      return console.log('for2', i);
    }), i < 5; setTimeout(function () {
      return console.log('for3', i);
    })) {
      _loop(i, _s);
    }
    

    Babel版运行结果:

    for1 5
    for2 5
    i 1
    for3 5
    for2 5
    i 2
    for3 5
    for2 5
    i 3
    for3 5
    for2 5
    i 4
    for3 5
    for2 5
    i 5
    for3 5
    for2 5
    

    与直接运行结果并不一致。 特别是直接运行结果最后连续输出5个5令人费解。

    先说闭包

    function f() {
    	let i = 0
      setTimeout(() => console.log(i))
      i++
    }
    

    闭包在JavaScript中可以解释为函数以及函数引用的上级作用域中的变量,上述例子就形成了闭包。 这里的函数是指箭头函数() => console.log(i),引用的变量则是i 在本篇中通过使用闭包可以在for循环全部结束后仍然保留对当前作用域下i的引用,从而让我们窥视for循环中作用域的分布。

    从头开始

    起源是我看见知乎上的一篇文章我用了两个月的时间才理解 let 简单回顾一下let的基础知识:

    for(var i = 0; i < 5; i++) {
      	// 依次输出5 5 5 5 5
        setTimeout(() => console.log(i))
    }
    
    for(let i = 0; i < 5; i++) {
      	// 依次输出0 1 2 3 4
        setTimeout(() => console.log(i))
    }
    

    这里无论是var还是let我们都只声明了一个变量,let本身并不能解释为何两者输出不一致。 真正的原因是for循环中的黑魔法。

    for循环中可以近似为:

    for(let i = 0; i < 5; i++) {
        // 每次循环重新声明
      	let _i = i
      	// 依次输出0 1 2 3 4
        setTimeout(() => console.log(_i))
    }
    

    但是我们都知道在循环体中对i重新赋值的话是可以影响到下一轮以及第二、三个表达式中的i的,所以在每轮循环后还需要将_i的值赋值给真实的i

    for(let i = 0; i < 5; i++) {
        // 每次循环重新声明
      	let _i = i
        
      	// 依次输出0 1 2 3 4
        setTimeout(() => console.log(_i))
      	_i++
      
      	// 循环体结束后将块级作用域的变量重新赋值到真实的变量
      	i = _i
    }
    

    于是我猜测这里假设的真实变量就是我们在for语句中第一个表达式声明的let i = 0,而第二、三表达式中的i < 5; i++则跟第一个表达式中的变量是同一个声明。

    《ECMAScript 6 入门》中写到:

    现在我们回到一开始的代码,Babel的编译完全符合我们至今的猜想:

    1. 使用每次循环体中重新声明变量
    2. 循环体结束后对真实的变量重新赋值
    3. for括号三个表达式中的变量为同一个声明
    // 上述JavaScript代码的babel编译结果
    "use strict";
    
    // 使用函数作用域重新声明了_i
    var _loop = function _loop(_i, _s) {
      setTimeout(function () {
        return console.log('i', _i);
      });
      _i++;
      // 循环体结束后对真实的i重新赋值
      i = _i;
    };
    
    // 三个表达式中的i为同一个声明
    for (var i = 0, _s = setTimeout(function () {
      return console.log('for1', i);
    }); setTimeout(function () {
      return console.log('for2', i);
    }), i < 5; setTimeout(function () {
      return console.log('for3', i);
    })) {
      _loop(i, _s);
    }
    

    分析真实运行结果

    for(let i = 0 , _s = setTimeout(() => console.log('for1', i)); 
        setTimeout(() => console.log('for2', i)), i < 5; 
        setTimeout(() => console.log('for3', i))
        ) {
        setTimeout(() => console.log('i', i))
        i++
    }
    

    运行结果

    for1 0
    for2 1
    i 1
    for3 2
    for2 2
    i 2
    for3 3
    for2 3
    i 3
    for3 4
    for2 4
    i 4
    for3 5
    for2 5
    i 5
    for3 5
    for2 5
    
    1. 使用每次循环体中重新声明变量

    这点在此得到了确认。

    1. 循环体结束后对真实的变量重新赋值
    2. for括号三个表达式中的变量为同一个声明

    而这两点在此得到了否定。 我们逐一分析。

    第一个表达式中: 最初声明的i从未改变,可以看出每次循环使用的全都是重新声明过的变量,并且没有将修改过的值重新赋值回第一个表达式中的i。 第二、三个表达式中: 和循环体中的变量一样,也是使用了重新声明的变量,可以认为每次重新声明后第二、三表达式和循环体使用同一个新变量。 这里问题的关键是重新声明变量的时机,如果按照我们印象中的理解: 第二表达式 -> 循环体 -> 第三表达式 这个顺序,然后在最开头重新声明变量的话,输出应为:

    for1 0
    for2 1
    i 1
    for3 1
    for2 2
    i 2
    for3 2
    for2 3
    i 3
    for3 3
    for2 4
    i 4
    for3 4
    for2 5
    i 5
    for3 5
    for2 5
    

    与实际结果不符。 想得到正确的结果,可以有多种理解,一种简单的理解是(不一定与V8实际行为一致): 第三表达式 -> 第二表达式 -> 循环体 只需要第一次循环时不执行第三表达式即可。

    使用代码描述如下:

    let i = 0
    setTimeout(() => console.log('for1', i))
    
    let _next = i, first = true
    while(true) {
        let _i = _next
        if(first) {
            first = false
        } else {
          	// 第三表达式
            setTimeout(() => console.log('for3', _i))
        }
    
      	// 第二表达式
        setTimeout(() => console.log('for2', _i))
        if(!(_i < 5)) break
    
      	// 循环体
        {
            setTimeout(() => console.log('i', _i))
            _i++
        }
        
        _next = _i
    }
    

    i为引用类型时也能保持一致:

    for(let x = { i: 0, t: '' }, _s = setTimeout(() => console.log('for1', x)); 
        setTimeout(() => console.log('for2', x)), x.i < 5; 
        setTimeout(() => console.log('for3', x)), x.i++) {
        setTimeout(() => {
            console.log('x', x)
        })
        x = { i: x.i, t: x.t + 'a' }
    }
    
    
    let x = { i: 0, t: '' }
    setTimeout(() => console.log('for1', x))
    
    let _next = x, first = true
    while(true) {
        let _x = _next
        if(first) {
            first = false
        } else {
            setTimeout(() => console.log('for3', _x))
            _x.i++
        }
    
        setTimeout(() => console.log('for2', _x))
        if(!(_x.i < 5)) break
    
        {
            setTimeout(() => console.log('x', _x))
            _x = { i: _x.i, t: _x.t + 'a' }
        }
    
        _next = _x
    }
    

    起源地下载网 » 你真的完全理解for中的let了吗,连Babel都不对!细说for的黑魔法

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元