setTimeout执行单次延迟任务,到点执行一次后自动销毁;setInterval启动周期性调度器,不手动清除会持续触发回调,易因回调超时导致任务堆积、节奏失控,推荐用递归setTimeout替代。

setTimeout 是“等一次就完事”,setInterval 是“每隔一阵就来一遍”
根本区别不在语法,而在执行模型:setTimeout 注册一个**单次延迟任务**,到点执行一次,自动销毁;setInterval 启动一个**周期性调度器**,只要不手动停,它就会按间隔不断尝试触发回调。
- 你写
setTimeout(fn, 1000),意思是“1 秒后调用fn一次”,之后再无后续 - 你写
setInterval(fn, 1000),意思是“从现在起,每 1 秒调用一次fn”,直到你调用clearInterval(id)或页面卸载 - 两者返回值都是数字 ID,必须用对应清除函数:
clearTimeout(id)/clearInterval(id)—— 混用会失效
为什么 setInterval 容易“越跑越快”?
因为 setInterval 不管上一次回调有没有执行完。如果回调耗时超过设定间隔(比如 setInterval(fn, 100),但 fn 平均要跑 150ms),浏览器会把后续回调“堆积”进任务队列,等前一个结束立刻执行下一个,造成密集连发、时间漂移甚至卡顿。
- 真实现象:倒计时从 “5→4→3” 突然跳成 “5→3→1”,或控制台疯狂打印日志
- 更稳的替代方案是递归
setTimeout:function tick() { doWork(); setTimeout(tick, 1000); // 下次执行由本次回调主动发起 } tick(); - 这样能确保「上一次执行完,才开始算下一次的 1000ms 延迟」,节奏可控
参数和传参方式,别踩字符串陷阱
现代写法统一推荐传函数引用 + 后续参数,避免用字符串形式(如 "fn()")—— 它会触发 eval,有安全风险、性能差、调试难,且无法正确捕获闭包变量。
- ✅ 正确(支持额外参数):
setTimeout(greet, 1000, 'Alice', 28) - ✅ 正确(箭头函数封装):
setTimeout(() => greet('Alice'), 1000) - ❌ 危险(字符串):
setTimeout("greet('Alice')", 1000) - ⚠️ 注意:
setInterval同样支持函数引用+参数,但别忘了清除逻辑必须在作用域内保留 ID 引用
不手动清除,就是内存泄漏的起点
定时器没被清除,它的回调函数和所有闭包引用的对象就一直活在内存里。尤其在单页应用中,组件卸载了但 setInterval 还在跑,轻则重复请求、重则崩溃。
立即学习“Java免费学习笔记(深入)”;
- 常见漏清除场景:React 组件
useEffect里启用了setInterval,却没在 cleanup 函数里调用clearInterval - 调试技巧:在定时器启动前打日志,在清除前也打,上线前加个
console.warn("interval still running!")防遗漏 - 小提醒:即使页面关闭,未清除的
setInterval也会持续占用资源,直到进程终止
setTimeout,而不是无脑选 setInterval。那个看似省事的“自动循环”,往往藏着最隐蔽的时序陷阱。











