题目描述:
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); //依次输出3,3,3
}, 1000);
}
for (let j = 0; j < 3; j++) {
setTimeout(function () {
console.log(j); //依次输出0,1,2
}, 1000);
为什么用let会输出0、1、2?
- 经百度以及个人思考,
- 得出的结果是
- 函数在声明时会同时保存当时的作用域链,就是说当时的活动变量
- 在setTimeout中使用变量时会沿着作用域链去寻找该变量,
对于用上面let 声明的循环来说,由于块级作用域,每次循环相当于--
{
let j = 0;
function () {
console.log(j);
}
}
//第二次循环
{
let j = 1;
function () {
console.log(j);
}
}
//第三次循环
{
let j = 2;
function () {
console.log(j);
}
这些块级作用域相互独立,互不影响
综上,用let,输出的才是0、1、2
问题
如果说是因为声明函数时作用域链就记录了当时的i值,那用var声明的就不会被记录吗?
还是我得到的答案错了,是因为别的原因导致输出不同?
对于所给两段for循环过程中,JavaScript围绕let变量、块级作用域、作用域链这几个关键词到底做了什么?
###- 每次执行到循环体中的
setTimeout
方法,该方法都会将调用的回调函数放入“任务队列中”,等待主线程(执行栈中)的事件全部执行完毕后,执行队列头的事件。 - 第一次循环后,任务队列中只有一个被
setTimeout
方法放入的回调函数,其作用域链中记录的是i的初始值0
- 由于let 声明的变量只存在于块级作用域内,因此每一次循环体执行完毕都会销毁该变量,然后在for循环出的新块中let声明一个新的变量j,按for循环原本既定的顺序为其赋值,然后执行循环体
- 因此第二次循环时,任务队列中的回调函数的作用域链中,记录的是新创建的,重新被赋值为
1
的变量i - 正是由于块级作用域的相互独立,互不影响,才不会覆盖j的值,就此,我有点理解为什么let能防止数据污染了(还有es6规定let不能重复声明这一点)
你对 let
的理解没有错,造成 var
的诡异行为的原因是早期 JS
没有块级作用域,那时候所用的关键字 var
声明的变量是在就近的函数作用域里。
所以
;(function(){
'use strict';
// var 在这里有效
if (true) {
var someUniqueVariation = 123;
}
console.log('someUniqueVariation:', someUniqueVariation);
})();
可以执行;
但
;(function(){
'use strict';
// let 在这里无效
if (true) {
// let 只在这里有效
let someUniqueVariation = 123;
}
console.log('someUniqueVariation:', someUniqueVariation);
})();
会报错。
###var声明的当然也被记录了,但是var由于是函数级作用域,在for循环里并不会重复产生新变量,而是原来的变量++,最后变成了3
###看一看一看这几篇
csdn
StackOverflow
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function() {
console.log(i);
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}//333
let a = [];
for (let j = 0; j < 3; j++){
a[j] = function() {
console.log("He value: "+ j)
}
}
for (let x = 0; x < 3; x++){
a[x]()
}///012
###其实背后的原理就是闭包,
闭包使得内部 setTimeout 对外部依赖的 j 值不被释放,
从而达到输出 0、1、2 的效果