JS循环打印问题

先看代码

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

输出结果为:打印10次10

实际上,var定义的变量在 for循环之外是可以访问到的,for 循环是同步操作,SetTimeout是异步。

执行步骤如下

1.for 循环(同步操作)快速完成所有 10 次迭代。

2.在每次迭代中,它设置了一个 setTimeout(异步操作)。

3.循环结束后,i 的值变为 10。

4.大约 1 秒后,事件队列中的 setTimeout 回调函数开始执行。

5.每个回调函数执行时,它们都引用同一个 i,而这时 i 的值已经是 10 了。

解决方案1:let定义块级作用域

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

原因

1.块级作用域: let 声明的变量具有块级作用域。在 for 循环中,每次迭代都会创建一个新的块级作用域。这意味着每次循环都会创建一个新的 i 变量。

2.闭包: 每个 setTimeout 的回调函数都形成了一个闭包,它可以访问到创建它时的作用域中的变量。

3.循环迭代中的行为: 使用 let 时,JavaScript 引擎会在每次循环迭代中创建一个新的 i 变量。每个 setTimeout 的回调函数都会捕获到当时迭代中的 i 值。

执行步骤如下

1、for 循环(同步操作)快速完成所有 10 次迭代。
2、在每次迭代中:

    创建一个新的块级作用域,包含当前迭代的 i 值。
    设置一个 setTimeout(异步操作)。
    setTimeout 的回调函数形成一个闭包,捕获当前迭代的 i 值。
3、循环结束后,外部的 i 不再可访问(因为 let 创建的是块级作用域)。
4、大约 1 秒后,事件队列中的 setTimeout 回调函数开始执行。
5、每个回调函数执行时,它们引用的是各自闭包中捕获的 i 值,这些值分别是 0 到 9。

let 的块级作用域特性确保了每个迭代的 i 值被正确地"冻结"在各自的闭包中。)

解决方案2:IIFE立即执行

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

 执行步骤:

1.for 循环快速执行 10 次迭代(同步操作)。
2.在每次迭代中:

   创建一个新的IIFE,并立即执行它(同步操作)。
   IIFE接收当前的 i 值作为参数。
   在IIFE内部,设置 setTimeout(异步操作)。
3.循环结束。约 1 秒后,setTimeout 回调开始执行,但每个回调都能访问到创建它时IIFE参数中的 i 值。

效果:1s后几乎同时打印0-9

解决方案3:Promise

const tasks = [];
for (var i = 0; i < 10; i++) {
  (function(i) {
    tasks.push(new Promise((resolve) => {
      setTimeout(() => {
        console.log(i);
        resolve();
      });
    }));
  })(i);
}
Promise.all(tasks).then(() => {
  setTimeout(() => {
    console.log(i); //i在for之外可被访问
  }, 1000);
});

 执行步骤:

1、创建空数组 tasks。
2、for 循环开始(同步操作):

    循环 10 次(i 从 0 到 9)。
    每次迭代都创建并立即执行一个 IIFE。
3、在每次 IIFE 执行时(同步操作):

    创建一个新的 Promise,在 Promise 内部设置一个 setTimeout(异步操作)。
    将这个 Promise 推入 tasks 数组。
4、循环结束,此时 i 的值为 10。
5、调用 Promise.all(tasks)(异步操作):

    等待 tasks 数组中的所有 Promise 解决。
6、设置 .then() 回调(异步操作):

    当所有 Promise 解决后,这个回调会被加入到事件队列。
7、主线程的同步操作全部完成,开始处理异步任务:

    10 个 setTimeout 回调几乎同时开始执行。
    每个回调打印出对应的 i 值(0 到 9),并解决对应的 Promise。
8、所有 Promise 解决后,Promise.all 的 .then() 回调执行:

    设置另一个 setTimeout,延迟 1000 毫秒。
9、大约 1 秒后,最后的 setTimeout 回调执行:

    打印 i 的值,此时 i 为 10。

效果就是:几乎同时打印出 0 到 9,大约 1 秒后,打印出 10

方案3的更简洁的写法

new Promise((resolve) => {
  for (var i = 0; i < 10; i++) {
    console.log(i);
    resolve();
  }
});

因为没有定时器,不存在异步操作,所以直接打印0-9;

resolve() 在第一次迭代就被调用,但这并不会中断循环的执行,函数会继续执行直到完成或遇到 return 语句。

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值