通过 setTimeout 示例解析 JavaScript 运行机制

作者:木棉时间:2026-10-23 07:03:27浏览:435

本文通过四个具体代码示例,深入解析 JavaScript 的单线程执行模型与异步任务处理机制。文章详细阐述了同步代码优先执行、异步任务进入队列以及定时触发后移入同步队列的执行顺序。针对循环中 setTimeout 输出相同值的常见问题,分别展示了利用闭包缓存变量和使用 ES6 let 块级作用域两种解决方案,帮助开发者理解变量作用域对异步回调的影响。

JavaScript 运行机制概述

前言

JavaScript 采用单线程模型,其执行流程具有独特的异步处理特性。

接下来,我们将通过几个关于 setTimeout 的具体案例,来揭示 JavaScript 运行机制中的关键细节。

示例1

同步与异步任务处理流程


console.log(1);
setTimeout(function(){
 console.log(2);
},0);
console.log(3);
// 打印出 1 3 2

示例2


console.log('1');
setTimeout(function(){
 console.log('2');
},0);
while(1){}
// 打印出1,然后浏览器卡死,不会打印出2

JavaScript 引擎会将待执行的内容放入任务队列中进行管理。

当遇到 setTimeout 时,系统将其识别为异步任务,并优先将其放入异步队列等待处理。

浏览器引擎会优先执行当前的同步任务,只有在同步任务完全执行完毕后,才会去检查异步队列的状态。

一旦异步队列中的任务到达设定的执行时机,浏览器便会将该任务移入同步队列等待执行。

即:

异步任务的执行时机一定晚于同步任务。

示例3

循环中 setTimeout 的变量共享问题


for(var i = 0; i < 4; i++){
 setTimeout(function() {
  console.log(i);
 }, 1000);
}
// 打印 4 4 4 4

为什么最终打印结果是 4 4 4 4 呢?

这是因为浏览器首先执行 for 循环逻辑。

在每次 for 循环迭代中,都会将一个 setTimeout 回调函数压入异步队列。

当 1000 毫秒过去,执行 setTimeout 内的方法时,变量 i 的值已经递增为 4。

如果要实现打印 0 1 2 3 的效果,该如何处理?

可以利用闭包特性,将变量 i 缓存到一个临时变量 temp 中。

利用闭包解决变量捕获


for(var i = 0; i < 4; i++){
 (function(temp){
  setTimeout(function() {
   console.log(temp);
  }, 1000);
 })(i);
}
// 打印 0 1 2 3

这种做法相当于在每次 for 循环中创建一个新的匿名函数,并将当前的 i 值存储在该匿名函数的内存空间中。

理解闭包概念的读者应该能轻松掌握这一原理。

示例4

我们知道 ES6 引入了新的关键字 let。

在这里,let 具备一项新的作用域特性。

ES6 let 块级作用域的应用


for(let i = 0; i < 4; i ++){
 setTimeout(function(){
  console.log(i); 
 }, 1000);
}
// 打印 0 1 2 3

示例4与示例3仅在 var 和 let 的使用上存在差异,但打印结果却截然不同。

这是因为 let 声明了块级作用域。

使用 let 定义的 i,对于每一次 for 循环的执行而言都是全新的变量(拥有独立的内存地址)。

因此,在打印时可以得到预期的 0 1 2 3 结果。

总结

以上就是本文的全部内容,希望这些内容对您的学习或工作有所帮助,感谢支持。

作者最新文章