ES6中let在for循环中的表现

836 阅读3分钟

前言

for (var i = 0; i < 6;i++) {
    setTimeout(function () {
        console.log(i)
    }, 1000)
}

我们都知道以上代码在ES5中会输出6个6。如果我们想要的结果是输出0 1 2 3 4 5,在ES6中可以将以上的var替换成let来得要想要的结果。那以下我们通过babel来将四段ES6代码编译成ES5来推测ES6中let在for中表现的原理。

代码片段一(将var替换成let)

编译前

for (let i = 0; i < 6; i++) {
  setTimeout(function () {
    console.log(i)
  }, 1000)
}

输出 0 1 2 3 4 5

编译后

"use strict";

var _loop = function _loop(i) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
};

for (var i = 0; i < 6; i++) {
  _loop(i);
}

结论:for循环内部(花括号内)是一个独立的作用域,babel编译后用函数作用域来模拟,通过类似传入函数参数的方式将i变量传入循环内部作用域中,因此setTimeout中使用的是每次循环时候的i的值。

代码片段二(在外部访问for循环中初始化的变量)

编译前

for (let i = 0; i < 6; i++) {
  setTimeout(function () {
    console.log(i)
  }, 1000)
}
console.log(i)

输出 0 1 2 3 4 5

输出 ReferenceError

编译后

"use strict";

var _loop = function _loop(_i) {
  setTimeout(function () {
    console.log(_i);
  }, 1000);
};

for (var _i = 0; _i < 6; _i++) {
  _loop(_i);
}
console.log(i);

结论:在for循环初始化中的用let定义的i不属于父级作用域,属于for循环的作用域,由于ES5中没有块级作用域,所以编译后只能用_i来区分。

代码片段三(将i定义在外部)

编译前

let i;
for (i = 0; i < 6; i++) {
  setTimeout(function () {
    console.log(i)
  }, 1000)
}

输出 6 6 6 6 6 6

编译后

var i = void 0;
for (i = 0; i < 6; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}

结论:当i定义在外部的时候,与ES5中的表现基本相同。

代码片段四(在循环内部修改i的值)

编译前

for (let i = 0; i < 6; i++) {
  i++
  setTimeout(function () {
    console.log(i)
  }, 1000)
}

输出 1 3 5

编译后

"use strict";

var _loop = function _loop(_i) {
  _i++;
  setTimeout(function () {
    console.log(_i);
  }, 1000);
  i = _i;
};

for (var i = 0; i < 6; i++) {
  _loop(i);
}

结论:若在for循环内部对传入的变量进行了修改,在每个内部循环结束时会将修改后的值重新赋值给这个变量。

总结

/* outer */
for (let i = 0 /* for */ ; i < n; i++) {
	/* inner */
}

不考虑for循环内部生成的其他作用域,通过let在for循环中初始化变量时会产生三类作用域,按从外而内的关系表示为 outer for inner,在上述代码中会产生 n 个相互独立的 inner 作用域。在 for 中的通过let初始化的变量(i),会以类似传递函数参数的形式被 inner 中代码使用,在一次 inner 中代码执行完成后,若传入的变量被改变,会将改变后的值重新赋值给 for 中的变量(i)。

如有不同意见,欢迎讨论。