很多刚开始用 JavaScript 的开发者,会觉得 setTimeout 是个让代码“等会儿再跑”的简单工具。但实际用起来,常常发现它不那么听话:明明设定了1秒后执行,结果却等了好几秒才来,或者干脆没动静。这背后的原因,弄明白了其实也不复杂。这篇文章就讲讲 setTimeout 到底是怎么工作的,以及为什么它有时会“迟到”或“缺席”,帮你避免常见的坑。
1. 最基础的误解:setTimeout(fn, 1000) 不等于“1秒后准时执行”
别被参数名字骗了。setTimeout 的真实意思是:“至少等1000毫秒后,把这个函数 fn 放进一个待办事项列表(任务队列)里排队。等当前手头所有活儿都干完了,再轮到它执行。”
看个简单例子就懂了:
console.log("开始");
setTimeout(() => { console.log("定时器到了!"); }, 0); // 延迟设为0
console.log("结束");
输出顺序是:
开始
结束
定时器到了!
为什么 0 秒延迟也没立刻执行?因为 setTimeout 的回调函数(就是那个打印“定时器到了!”的函数)被放进队列后,必须等到当前正在跑的代码(就是打印“开始”和“结束”的那段)全部执行完毕,主线程闲下来了,才会去队列里把它拿出来执行。即使延迟是0,也要排队。
2. 主线程忙不过来,定时器就得一直等(延迟漂移)
JavaScript 在一个线程里干活(单线程)。如果这个线程被其他事情卡住了,setTimeout 设定的时间到了也没用,回调函数只能在队列里干等着。
看这个例子:
console.log("开始");
setTimeout(() => { console.log("Timeout!"); }, 1000); // 计划1秒后执行
const startTime = Date.now();
while (Date.now() - startTime < 3000) { // 模拟一个耗时3秒的复杂计算
// 这里啥也不干,就是空等3秒,占住主线程
}
console.log("结束");
输出顺序和大概时间是:
开始
(这里卡顿3秒...)
结束
Timeout! (在 "开始" 打印后大约3秒多才出现)
虽然我们设定了1秒后执行 console.log("Timeout!"),但主线程被那个 while 循环死死占住了整整3秒。定时器的时间(1秒)到了,回调函数被放进了队列,但只能眼巴巴等着主线程空闲。直到3秒后循环结束,打印了“结束”,主线程才空闲下来,这时它才去队列里拿出回调函数执行。结果就是,1秒的延迟实际变成了3秒多。这就是所谓的“延迟漂移”。
3. 用 setTimeout 搞循环定时?小心误差越来越大
有时候你想每隔1秒重复做点事,可能这样写:
function doSomething() {
console.log("干活了...");
// 假设这里可能也有点耗时操作...
setTimeout(doSomething, 1000); // 干完再计划下一次
}
doSomething(); // 启动
这个写法的问题是:setTimeout 是在 doSomething 函数执行完之后,才计划下一次执行。如果 doSomething 函数本身执行需要时间(比如200毫秒),那么两次执行之间的间隔就变成了 1000毫秒 + 200毫秒 = 1200毫秒。时间一长,这个误差会累积,定时就越来越不准了。
想更准一点怎么办?
setInterval: 这个函数就是设计来重复执行的。它会尝试每隔指定的时间(比如1000ms)就把回调函数放进队列一次。但是,它有个问题叫“累积效应”:如果回调执行时间比间隔时间长,那么下一次回调会立刻执行(或者连续堆积起来),而不是等间隔时间。所以它也不是绝对精确。
requestAnimationFrame (做动画首选): 浏览器专门为流畅动画设计的。它会在每次屏幕刷新前调用你的函数(通常是每秒60次)。用它做动画最合适,能保证流畅度,并且浏览器在标签页不可见时会自动暂停,省资源。
基于时间差计算: 对于需要精确时间间隔但又不能用 requestAnimationFrame 的情况(比如不是动画),可以在每次回调里记录实际过去的时间,然后计算下一次需要执行的时间点。这能减少累积误差。
4. 我的定时器怎么根本没执行?
如果 setTimeout 的回调函数压根没跑,通常不是语法错误,而是这些情况:
页面或组件卸载了: 在 react, vue 这些框架里,如果你在一个组件里设置了定时器,但组件被销毁(比如页面跳转、组件隐藏)时没清除它,定时器虽然可能还在,但回调函数执行时组件状态可能已经无效了,导致看起来没执行,甚至报错。关键:组件卸载前务必用 clearTimeout 清理定时器!
被清除了: 你或者某个库的代码主动调用了 clearTimeout,传入了正确的定时器ID。
浏览器标签页在后台: 为了省电和省资源,现代浏览器(Chrome, Firefox, Safari 等)会对后台标签页(不是当前你看的那个标签)里的定时器进行“节流”。延迟很短(比如几秒)的定时器可能被延迟执行,非常短的(如动画用的)可能被暂停。Chrome 对后台标签页的非活动定时器,延迟时间可能被限制到至少1分钟(1000ms以上)甚至更长(如10分钟)!当你切回标签页时,它们才会被处理。
环境不同: 你的代码可能运行在 Web Worker 或 Node.js 环境里,它们处理定时器的机制和浏览器主线程不完全一样。
5. 循环里的定时器和闭包陷阱(为什么都打印5?)
这是一个非常经典的坑:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 期待0,1,2,3,4?实际输出:5,5,5,5,5
}, 1000);
}
为什么输出5个5?
var 声明的 i 是函数作用域(在这个例子里相当于全局作用域),不是块级作用域。
循环飞快地跑了5次,创建了5个定时器(都设定了1秒后执行)。
循环结束后,i 的值已经是 5。
1秒后(或者更久,看主线程忙不忙),5个定时器的回调函数开始执行。
它们访问的都是循环结束后同一个 i,也就是 5。
怎么修?两种常用方法:
用 let(推荐): let 是块级作用域。每次循环都会创建一个新的 i,每个定时器回调都绑定了自己循环那次的那个 i 的值。
for (let i = 0; i < 5; i++) { // 关键:把 var 改成 let
setTimeout(function() {
console.log(i); // 正确输出:0, 1, 2, 3, 4
}, 1000);
}
用 IIFE(立即执行函数表达式)创建作用域: 如果你不得不用 var,可以用 IIFE 在每次循环时创建一个新的作用域,把当前的 i 值“锁”住。
for (var i = 0; i < 5; i++) {
(function(j) { // j 捕获了当前循环的 i 值
setTimeout(function() {
console.log(j); // 正确输出:0, 1, 2, 3, 4
}, 1000);
})(i); // 立即调用,把当前的 i 传进去作为 j
}
总结:理解 setTimeout 的关键点
核心概念 | 解释说明 |
---|---|
不是倒计时器 | setTimeout(fn, 1000) 意思是“至少等1000毫秒后,把 fn 放进任务队列”,不是“1000毫秒后准时执行”。 |
要排队等空闲 | 放进队列的 fn,必须等当前所有代码执行完(主线程空闲)才会被执行。 |
主线程是老大 | 如果主线程被其他任务(复杂计算、长循环、渲染)卡住,定时器回调就得一直等着,导致“延迟漂移”。 |
循环里有坑(var) | 在 for 循环里用 var 配合 setTimeout,所有回调会共享循环结束后的变量值。用 let 或 IIFE 解决。 |
后台会被限制 | 浏览器会大幅延迟后台标签页的定时器执行(节能),可能导致回调长时间不执行。 |
用完要清理 | 在单页应用(SPA)或组件中,组件卸载前必须用 clearTimeout 清除定时器,避免错误或内存泄漏。 |
简单说:
别把 setTimeout(fn, 1000) 理解成“请1秒后执行这个函数”。它其实是说:“1秒后,把这个函数加到待办事项里,等手头没活了再处理它。” 手头有没有活、活多不多、浏览器标签是不是在前台,都会影响它最终执行的时间。理解了这一点,再用 setTimeout 就能避开大部分坑了。
这篇文章将带你深入理解js中定时器是如何工作的,setTimeout和setInterval的原理是什么?
在开发一个在线聊天工具时,经常会有过多少毫秒就重复执行一次某操作的需求。“没问题”,大家都说,“用setInterval好了。”我觉得这个点子很糟糕。
之前印象中一直记得setInterval有一些坑,但是一直不是很清楚那些坑是什么。setInterval会无视代码的错误、setInterval会无视任何情况下定时执行、、setInterval不能确保每次调用都能执行
setInterval()和setTimeout()方法都是js原生的定时方法,当然它们两个的作用也是不同的,并且最近在做上下滚动公告栏的时候,发现了setInterval()非常令人抓狂的问题,那就是用setInterval()做的定时滚动会随着浏览器页面切换变得无法控制!为什么会说无法控制呢
setTimeout()函数:用来指定某个函数或某段代码在多少毫秒之后执行。它返回一个整数,表示定时器timer的编号,可以用来取消该定时器。JavasScript引擎是基于事件驱动和单线程执行的,JS引擎一直等待着任务队列中任务的到来
用Cron表达式完成定时器,全局内关闭定时器需要获取到定时器的引用,scheduleJob存在第四个参数,然而readme中没有提及,可知API
主要是利用定时器,点击开始IDE时候不断的执行,并同时生成随机数,利用数组的下标完成展示。主要用到的知识点:setInterval,Math.random()
JS提供了一些原生方法来实现延时去执行某一段代码,下面来简单介绍一下setTiemout、setInterval、setImmediate、requestAnimationFrame。JS提供了一些原生方法来实现延时去执行某一段代码,下面来简单介绍一下。
之前在项目中写了定时器来做循环播放,但是总是会有越走越快的问题,开始是以为前后的HTML代码拼接的有问题,时间紧急的情况下反复改了很多也没什么效果,后来发现是js定时器的问题,在这里记录一下。
JS提供了一些原生方法来实现延时去执行某一段代码,下面来简单介绍一下setTiemout、setInterval、setImmediate、requestAnimationFrame。setTimeout: 设置一个定时器,在定时器到期后执行一次函数或代码段
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!