JSweb前端技术分享

67.for循环中的var 和 let

2022-02-27  本文已影响0人  wo不是黄蓉

day16:
这段代码我们应该都见过,想要的效果是我们每隔一秒钟分别打印出来1,2,3,4,5。然而,效果却是打印了5,5,5,5,5。但是我们把声明i的声明语句改成let就可以这是为什么呢?

for (var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  }, i*1000);
}

原因是因为,var声明的变量会被变量提升,上面一段代码相当于

var i;
for (i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  }, i*1000);
}

此时i就是全局对象中的一个属性,即是window下的,因此我们每次修改执行完i的值之后,在异步任务还未执行到的时候for循环已经执行完成了,此时i的值已经被修改成了5,因此每次输出的都是5.
那么为什么使用let就可以解决呢?
因为let声明的变量会存在一个块级作用域的概念,使用let声明迭代变量时,js引擎会在后台为每一个迭代循环声明一个新的迭代变量,因此每次使用的i算是不同的。
如何不使用let来解决var变量作用域相同的问题呢?
使用闭包
产生闭包的条件:

for (var i = 0; i < 5; i++) {
//2.a的结果时一个函数
  let a = creater();
//3.执行i,此时i的值还未改变,此时另一个函数作用域中引用了i变量的值,因此i并不会就此销毁而是会被延用到a函数这个作用域中
  a(i);
}

//1.creater相当于就是一个工厂函数,用来延长i的作用域
function creater() {
//4.此时参数的i就是a中的i。在创建函数时会创建一个作用域链,此时参数中的i指向的是a中的i,而a中的i又是全局中的i,在函数创建过程中会预装载全局变量对象,并保存到内部的scope中。此时就创建了一个新的空间,因此全局变量的修改就不会影响到i的改变。
  return function (i) {
    setTimeout(() => {
      console.log(i);
    }, i*1000);
  };
}

分析过程看上面代码注释。
何以见得全局变量的修改就不会影响到i的改变?

for (var i = 0; i < 5; i++) {
  let a = creater();
  a(i);
}

console.log(i + " start");
i = 10;
function creater() {
  return function (i) {
    setTimeout(() => {
      console.log(i);
    }, i * 1000);
  };
}
console.log(i + " end");

打印i时,会发现i的值已经改为了10,发现start之前未5,end时是10.说明此时修改的i就是全局变量中的i。

还有一种常见的修改思路:使用IIFE。原理相同不多做分析。

for (var i = 0; i < 5; i++) {
  (function (i) {
    setTimeout(() => {
      console.log(i);
    }, 1000);
  })(i);
  creater(i);
}
上一篇下一篇

猜你喜欢

热点阅读