javascript定时器_setTimeout和setInterval有何区别

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 还在跑,轻则重复请求、重则崩溃。

  • 常见漏清除场景:React 组件 useEffect 里启用了 setInterval,却没在 cleanup 函数里调用 clearInterval
  • 调试技巧:在定时器启动前打日志,在清除前也打,上线前加个 console.warn("interval still running!") 防遗漏
  • 小提醒:即使页面关闭,未清除的 setInterval 也会持续占用资源,直到进程终止
实际开发中,绝大多数需要“重复执行”的逻辑,都该优先考虑递归 setTimeout,而不是无脑选 setInterval。那个看似省事的“自动循环”,往往藏着最隐蔽的时序陷阱。