javascript - 怎么理解for循环中用let声明的迭代变量每次是新的变量?

查看:150
本文介绍了javascript - 怎么理解for循环中用let声明的迭代变量每次是新的变量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问 题

背景

最近在总结基础知识,然后看了阮一峰老师的es6教程,其中谈及let以及块级作用域的时候,举了一个经典的例子,代码如下:

var a = [];
for (var i = 0; i < 10; i++) {
  // 作用域a
  a[i] = function () {
    // 作用域b
    console.log(i);
  };
}
a[6](); // 10

因为es5不存在块级作用域,所以迭代变量i泄露了,然后对于a数组内每一个函数内的i都是向上查询作用域a的,所以结果是10。这个没问题。

下面的例子是用let来声明迭代变量的

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

老师是这样解释的

上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。

疑问
我想不通每一次循环的i其实都是一个新的变量这个过程是怎么样的,如果我理解为每次迭代都是新的一个块级作用域,那么迭代变量的迭代(i++)是如何传递给下一个块级作用域呢?

自知之明
虽然我知道结果,也知道这样的问题是转牛角尖,就是好奇问问。希望各路英雄指点迷津。


2016.11.20 21:00
刚刚想到了一个办法,尝试看看es6经过babel如何转化成es5的。所以大家可以看看转码过后是这样的:

"use strict";

var a = [];

var _loop = function _loop(i) {
  a[i] = function () {
    console.log(i);
  };
};

for (var i = 0; i < 10; i++) {
  _loop(i);
}
a[6](); // 6

就像用var声明迭代变量的时候,用iife来充当块级作用域一样。但是转到let上,我似乎理解不到迭代变量是如何传递的

// 在执行for循环的时候,我能这么理解吗?
{ let i = 0; 
 {
  a[i] = function () {
    console.log(i);
  };
  i++;
 }
}


2016.11.21 5:57
结合网友 @边城 & @eyesofkids 的回答,我的理解是:

var a = [];
{ let k = 0;  
    for (;k < 10;) {
      let i = k; // 这一步是内部进行转换的,可以看看下面我对 @边城 的神奇代码的理解
      a[i] = function () {
        console.log(i);
      };
      console.log("in block", i);
      console.log("in for expression", k);
      k++;
    }
}
a[6](); // 6

这样的结构,可以用 @边城 的神奇代码来检测:

for (let i = 0 /* 作用域a */; i < 3; console.log("in for expression", i), i++) {
    let i; //这里没有报错,就意味着这里跟作用域a不同,换做k可能更好理解
    console.log("in for block", i);
}

// 运行结果如下
in for block undefined
in for expression 0
in for block undefined
in for expression 1
in for block undefined
in for expression 2

for (let i = 0; i < 3; console.log("in for expression", i), i++) {
    let k;
    console.log("in for block", k);
}

// 运行结果如下
in for block undefined
in for expression 0
in for block undefined
in for expression 1
in for block undefined
in for expression 2

解决方案

这是在for语句中的var与let的差异:

for (let x...)的循环在每次迭代时都为x创建新的绑定

以下用代码直接看会比较容易的理解。这个改进主要是为了要解决在for语句中的闭包结构的问题。

原来的使用var的代码,与去糖(desugar)后来看它在执行时是这样的模拟代码:

//原来代码
for (var i = 0; i < 10; i++) { setTimeout(()=>console.log("i:",i), 1000); }

// 不需要加区块符,因为区块也不会影响
var i;
i = 0;
if (i < 10)
    setTimeout(()=>console.log("i:",i), 1000);
    i++;
    if (i < 10)
        setTimeout(()=>console.log("i:",i), 1000);
        i++;
//...

而使用了let后,会有块级作用域的影响,原来的代码与执行时的去糖模拟代码如下:

// 原来代码
for (let i = 0; i < 10; i++) { setTimeout(()=>console.log("i:",i), 1000); }

// 用区块符区分每次循环的语句
// 每次for语句开始,i指定为一个全域刻度__status,这只是方便说明而已
// __status会记录for语句i最后的值
{ let i;
  i = 0;
  __status = {i};
}
{ let {i} = __status;
  if (i < 10)
      setTimeout(()=>console.log("i:",i), 1000);
      __status = {i};
}   
    { let {i} = __status;
      i++;
      if (i < 10)
          setTimeout(()=>console.log("i:",i), 1000);
          __status = {i};
    }
    //...


为何可以这样模拟?因为在ES标准中,有一段是关于CreatePerIterationEnvironment,也就是for语句每次循环所要建立环境的步骤,里面有提及有关词法环境的相关步骤(LexicalEnvironment),这与使用let时会有关。所以,如果你使用了let而不是var,let的变量除了作用域是在for区块中,而且会为每次循环执行建立新的词法环境(LexicalEnvironment),拷贝所有的变量名称与值到下个循环执行。以最简单的方式改写原先的问题中的代码,相当于下面这样写:

var a = [];
{ let k;
    for (k = 0; k < 10; k++) {
      let i = k; //注意这里,每次循环都会创建一个新的i变量
      a[i] = function () {
        console.log(i);
      };
    }
}
a[6](); // 6

这篇关于javascript - 怎么理解for循环中用let声明的迭代变量每次是新的变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆