JavaScript let 块级作用域对作用域链的影响?

Crushdada
  • 80

题目描述:

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
    问题

对于所给两段for循环过程中,JavaScript围绕let变量、块级作用域、作用域链这几个关键词到底做了什么?

回复
阅读 2.5k
7 个回答
  • 每次执行到循环体中的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 的效果

for (var i = 0; i < 3; i++) {  
      setTimeout(function () {
        console.log(i);  //依次输出3,3,3
      }, 1000);
}

等同于

var i
for (i=0; i < 3; i++) {  
      setTimeout(function () {
        console.log(i);  //依次输出3,3,3
      }, 1000);
}

是不是可以简单的理解为let实在每次循环变量i已经打包到每个定时器里面了,而var相当于定时器执行的时候再去上下文查找

你知道吗?

宣传栏