JS 函数的执行时机

现有代码如下:

1
2
3
4
5
6
let i = 0;
for (i = 0; i < 6; i++) {
setTimeout(() => {
console.log(i);
}, 0);
} //输出6个6

为什么会输出 6 个 6 而不是 0、1、2、3、4、5 呢?这里只要明确一点:JS 语言的一大特征是单线程,而 setTimeout 是异步执行的;再结合阮一峰老师的这篇博客 JavaScript 运行机制详解:再谈 Event Loop - 阮一峰的网络日志 (ruanyifeng.com) ,就能大致明白是怎么回事了,简单来说因为 setTimeout 是异步执行的,这个和设置的延迟时间没有关系,设置成 0 还是 10000 都是一样在执行过程中被认为是异步任务,而异步任务必须等待主线程上排队执行的同步任务执行完成,系统开始读取任务队列之后开始执行,而此时 i 在主线程上经历了 for 循环之后,里面的值已经累加到了 6,所以此时异步任务再执行输出自然就是 6 个 6 了。

如果再执行如下代码:

1
2
3
4
5
6
for (let i = 0; i < 6; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
//输出 0 1 2 3 4 5

为什么这里输出又变成了 0,1,2,3,4,5 呢?这里是因为let 的块级作用域造成的,和第一段代码不同,这里的 i 被定义在循环体内部,因为 for 循环了 6 次,相当于产生了 6 个块级作用域,而在每个块级作用域里面,i 的值分别是 0,1,2,3,4,5,当系统开始执行任务队列里的异步任务时,就会将 i 的这 6 个值输出,就是 0,1,2,3,4,5 了。

那么还有其他方法可以做到输出 0,1,2,3,4,5 吗?在网上找到了如下方法:

1
2
3
4
5
6
7
8
9
10
11
//第一种
let i = 0;
for (i = 0; i < 6; ++i) {
setTimeout(
function (i) {
console.log(i);
},
0,
i
);
}

这种方法利用了给 setTimeout 传参来实现,详情参考此博客关于 setTimeout()你所不知道的地方,详解 setTimeout()-前端开发博客 (caibaojian.com)

1
2
3
4
5
6
7
8
//第二种
for (var i = 0; i < 6; ++i) {
!(function (j) {
setTimeout(function () {
console.log(j);
}, 0);
})(i);
}

创建立即执行函数,将 i 传到立即执行函数中产生独立的作用域,也可以实现输出 0,1,2,3,4,5。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!