setTimeout只执行一次,适合防抖、按钮禁用等场景;需递归调用才能实现循环;setInterval会持续执行直至手动清除,易因回调耗时导致堆积和内存泄漏;推荐用递归setTimeout替代setInterval以保障节奏稳定和生命周期可控。
你写 setTimeout(fn, 1000),就是告诉浏览器:“1 秒后调用 fn 一次,之后就没了”。它不会自己续费,也不会管你有没有清掉——它执行完就自动释放资源。
setTimeout(console.log, 500, "hello", "world"),比 setTimeout(() => console.log("hello", "world"), 500) 更干净function tick() { doWork(); setTimeout(tick, 1000); } —— 这才是可控节奏的起点setInterval(fn, 1000) 的意思是:“从现在起,每 1 秒就尝试调用 fn 一次”,它不看上一次是否结束、有没有报错、甚至页面是否还活着。
fn 执行耗时 > 1000ms(比如请求慢、渲染重),浏览器会把后续调用“堆进队列”,等前一个结束立刻连发,造成节奏崩坏clearInterval(id) 就是内存泄漏:回调函数 + 闭包引用的对象全被锁住,组件卸载了还在后台发请求clearInterval() 只能清 setInterval() 返回的 ID,拿 clearTimeout() 去清会完全无效不是为了炫技,而是为了解决真实问题:时间漂移、回调堆积、难以调试。
setTimeout 天然保证“上一次执行完,才开始算下一次延时”,节奏稳定,不怕回调变慢setInterval 回调里抛错,定时器照常触发,错误被吞掉,只剩控制台一行警告useEffect cleanup 里写 clearTimeout(timerId) 就够了,不用额外存 reflet timerId = null;
function poll() {
fetch("/api/status").then(r => r.json()).then(data => {
updateUI(data);
});
timerId = setTimeout(poll, 3000); // 成功/失败后都重新计时
}
timerId = setTimeout(poll, 3000);

90% 的内存泄漏和重复执行 bug,都出在这几步。
clearTimeout() 清 setTimeout() 的返回值,clearInterval() 清 setInterval() 的返回值,混用=白清useEffect 的 cleanup 函数、Vue 的 onBeforeUnmount、原生 beforeunload 事件里都要检查并清除setTimeout。那个看似省事的 setInterval,往往在压力测试或弱网环境下第一个暴露问题。