【JavaScript】setTimeout和setInterval中的陷阱

✅ 一、核心区别回顾

setTimeout(fn, delay)      // delay 毫秒后执行 fn,一次
setInterval(fn, delay)     // 每隔 delay 毫秒执行 fn,循环执行

⚠️ 二、常见陷阱和注意事项

1. 定时不准(延时不准确)

现象:

你设置了 setTimeout(fn, 1000),但实际执行时间可能远远大于 1000ms。

原因:

JavaScript 是单线程的,setTimeoutsetInterval 都是任务调度器,它们的时间是最短延时,不是准确执行时间。

取决于事件循环,回调需要等待主线程执行完毕。浏览器中常见的最小延迟时间为 4ms ,避免过渡消耗资源。

setTimeout(() => {
  console.log('Start');
}, 1000);

// 如果前面有一个长时间任务
const now = Date.now();
while (Date.now() - now < 3000) {} // 阻塞3秒

➡️ setTimeout 实际会在 3 秒后才执行。


2. setInterval 累积延迟 & 非精确调度

原因:

setInterval 并不等待上一个任务完成,而是按固定时间调度下一次:

setInterval(() => {
  const start = Date.now();
  while (Date.now() - start < 1500) {}
  console.log('执行了一次');
}, 1000);

➡️ 执行任务需要 1.5s,但是设置interval 为 1s ,回调执行时间 .> delay。输出结果不是每隔 1 秒,而是任务积压执行,可能造成线程拥堵,主线程空闲时,任务接连执行。

更推荐使用递归 setTimeout 实现“精准 setInterval”

这样下一次任务的执行总是基于上一次任务完成后的时间来计算。

function repeat() {
  setTimeout(() => {
    console.log('更精准的定时任务');
    repeat(); // 递归调用
  }, 1000);
}
repeat();

3. this 指向问题

const obj = {
  name: 'Alice',
  sayHi() {
    setTimeout(function () {
      console.log(this.name); // undefined
    }, 1000);
  }
};

obj.sayHi();
原因:

普通函数内的 this 指向全局对象(严格模式下是 undefined

✅ 解决方法:

// 方法1:使用箭头函数
setTimeout(() => {
  console.log(this.name);
}, 1000);

// 方法2:bind(this)
setTimeout(function () {
  console.log(this.name);
}.bind(this), 1000);

4. 内存泄漏风险(setInterval 忘记清除)

setInterval(() => {
  // 持续引用某个DOM或数据对象
}, 1000);
// 没有 clearInterval => 永远运行,占用内存

✅ 解决方式:

const id = setInterval(fn, 1000);
// 在合适的时候清除
clearInterval(id);

5. setTimeout 最小延时是 4ms(嵌套层次高于5)

// 嵌套层数 >= 5 时,延迟会被强制设置为至少 4ms(浏览器安全策略)

6. setTimeout(fn, 0) ≠ 立即执行

虽然你写了 0 毫秒延时,实际上它会在当前调用栈清空后再执行。

console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
// 输出:1 -> 3 -> 2

✅ 三、实战建议

场景 建议
需要周期任务,且任务耗时很短 可用 setInterval
需要精准间隔、避免堆积 使用递归 setTimeout
需要延迟执行一次任务 setTimeout(fn, delay)
需要手动停止 保留定时器ID,使用 clearTimeout / clearInterval

你可能感兴趣的:(JavaScript,javascript,前端,vue.js)