阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 定时器与延时器

定时器与延时器

作者:陈川 阅读数:57946人阅读 分类: JavaScript

定时器与延时器的基本概念

JavaScript提供了两种主要的定时机制:setTimeoutsetIntervalsetTimeout用于在指定时间后执行一次回调函数,而setInterval则会按照固定的时间间隔重复执行回调函数。这两种方法都返回一个唯一的ID,可用于后续清除定时器。

// setTimeout示例
const timeoutId = setTimeout(() => {
  console.log('这段代码将在1秒后执行');
}, 1000);

// setInterval示例
const intervalId = setInterval(() => {
  console.log('这段代码每1秒执行一次');
}, 1000);

setTimeout的详细用法

setTimeout函数接受两个主要参数:回调函数和延迟时间(毫秒)。第三个及以后的参数会作为回调函数的参数传入。

function greet(name, age) {
  console.log(`你好,${name},你今年${age}岁`);
}

setTimeout(greet, 2000, '张三', 25);

延迟时间的最小值在不同浏览器中可能有所不同,通常为4ms。如果设置为0,回调函数会被放入事件队列,等待当前执行栈清空后立即执行。

console.log('开始');
setTimeout(() => {
  console.log('setTimeout回调');
}, 0);
console.log('结束');
// 输出顺序:开始 → 结束 → setTimeout回调

setInterval的深入解析

setInterval会按照指定的时间间隔重复执行回调函数,直到被清除。需要注意的是,它不考虑回调函数的执行时间,可能导致执行重叠。

let counter = 0;
const intervalId = setInterval(() => {
  counter++;
  console.log(`执行次数:${counter}`);
  if (counter >= 5) {
    clearInterval(intervalId);
  }
}, 1000);

对于需要精确间隔的场景,可以考虑使用递归的setTimeout

function repeat(func, interval) {
  function wrapper() {
    func();
    setTimeout(wrapper, interval);
  }
  setTimeout(wrapper, interval);
}

repeat(() => {
  console.log('精确间隔执行');
}, 1000);

清除定时器的方法

使用clearTimeoutclearInterval可以分别取消setTimeoutsetInterval设置的定时器。

const timeoutId = setTimeout(() => {
  console.log('这段代码不会执行');
}, 5000);

// 3秒后取消定时器
setTimeout(() => {
  clearTimeout(timeoutId);
}, 3000);

在组件卸载或页面离开时清除定时器是个好习惯:

// React组件示例
useEffect(() => {
  const intervalId = setInterval(() => {
    // 执行某些操作
  }, 1000);
  
  return () => {
    clearInterval(intervalId);
  };
}, []);

定时器的实际应用场景

  1. 延迟执行:用户停止输入后执行搜索
let searchTimeout;
searchInput.addEventListener('input', (e) => {
  clearTimeout(searchTimeout);
  searchTimeout = setTimeout(() => {
    performSearch(e.target.value);
  }, 500);
});
  1. 轮询:定期检查服务器状态
function checkServerStatus() {
  fetch('/api/status')
    .then(response => response.json())
    .then(data => {
      updateStatus(data);
      setTimeout(checkServerStatus, 5000);
    });
}
checkServerStatus();
  1. 动画:简单的帧动画
function animate(element, duration) {
  const start = Date.now();
  function frame() {
    const elapsed = Date.now() - start;
    const progress = Math.min(elapsed / duration, 1);
    element.style.opacity = progress;
    if (progress < 1) {
      requestAnimationFrame(frame);
    }
  }
  frame();
}

定时器的高级技巧

  1. 动态调整间隔:根据条件改变执行频率
let interval = 1000;
const adjustInterval = () => {
  const id = setInterval(() => {
    console.log(`当前间隔:${interval}ms`);
    if (someCondition) {
      clearInterval(id);
      interval = 2000;
      adjustInterval();
    }
  }, interval);
};
adjustInterval();
  1. 批量操作:避免频繁触发
const batchProcess = (() => {
  let queue = [];
  let isProcessing = false;
  
  return (item) => {
    queue.push(item);
    if (!isProcessing) {
      isProcessing = true;
      setTimeout(() => {
        processQueue(queue);
        queue = [];
        isProcessing = false;
      }, 100);
    }
  };
})();
  1. 精确计时:补偿计时误差
function preciseInterval(callback, interval) {
  let expected = Date.now() + interval;
  const timeout = () => {
    const drift = Date.now() - expected;
    callback();
    expected += interval;
    setTimeout(timeout, Math.max(0, interval - drift));
  };
  setTimeout(timeout, interval);
}

定时器的性能考量

长时间运行的定时器可能影响页面性能,特别是在移动设备上。浏览器会对后台标签页或不活动页面的定时器进行节流。

// 检测定时器是否被节流
let last = Date.now();
setInterval(() => {
  const now = Date.now();
  console.log(`实际间隔:${now - last}ms`);
  last = now;
}, 1000);

对于动画等高性能要求的场景,优先使用requestAnimationFrame

function animate() {
  // 动画逻辑
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

定时器与事件循环

理解定时器需要了解JavaScript的事件循环机制。定时器回调会被放入任务队列,等待调用栈清空后执行。

console.log('脚本开始');
setTimeout(() => {
  console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
  console.log('Promise');
});
console.log('脚本结束');
// 输出顺序:脚本开始 → 脚本结束 → Promise → setTimeout

微任务(如Promise)会在宏任务(如定时器)之前执行,这解释了上面的输出顺序。

定时器的替代方案

现代JavaScript提供了几种定时器的替代方案:

  1. requestAnimationFrame:适合动画
function animate() {
  // 动画逻辑
  if (!shouldStop) {
    requestAnimationFrame(animate);
  }
}
animate();
  1. IntersectionObserver:元素可见时执行
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // 元素进入视口时执行
    }
  });
});
observer.observe(document.querySelector('.target'));
  1. Web Workers:后台长时间运行任务
const worker = new Worker('worker.js');
worker.postMessage('开始工作');
worker.onmessage = (e) => {
  console.log('收到消息:', e.data);
};

定时器的常见陷阱

  1. this绑定问题
const obj = {
  value: 42,
  showValue() {
    console.log(this.value); // 错误:this指向全局对象
  }
};
setTimeout(obj.showValue, 1000); // 输出undefined

// 解决方案
setTimeout(() => obj.showValue(), 1000); // 正确
setTimeout(obj.showValue.bind(obj), 1000); // 正确
  1. 闭包问题
for (var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i); // 总是输出5
  }, 1000);
}

// 解决方案
for (let i = 0; i < 5; i++) { // 使用let
  setTimeout(() => {
    console.log(i); // 0,1,2,3,4
  }, 1000);
}
  1. 内存泄漏
function startTimer() {
  const data = getHugeData();
  setInterval(() => {
    process(data); // data不会被释放
  }, 1000);
}

// 解决方案
function startTimer() {
  setInterval(() => {
    const data = getHugeData(); // 每次重新获取
    process(data);
  }, 1000);
}

定时器的调试技巧

  1. 给定时器命名
const id = setTimeout(function timerHandler() {
  // 在调试器中可以看到函数名
}, 1000);
  1. 记录定时器堆栈
function createTraceableTimeout(callback, delay) {
  const stack = new Error().stack;
  return setTimeout(() => {
    try {
      callback();
    } catch (e) {
      console.error('定时器出错,创建时的堆栈:', stack);
      throw e;
    }
  }, delay);
}
  1. 监控定时器数量
const originalSetTimeout = window.setTimeout;
let activeTimers = 0;

window.setTimeout = (callback, delay) => {
  activeTimers++;
  const id = originalSetTimeout(() => {
    activeTimers--;
    callback();
  }, delay);
  console.log(`当前活跃定时器: ${activeTimers}`);
  return id;
};

Node.js中的定时器

Node.js环境提供了额外的定时器功能:

  1. setImmediate:在当前事件循环结束时执行
setImmediate(() => {
  console.log('立即执行');
});
  1. process.nextTick:在当前操作完成后执行
process.nextTick(() => {
  console.log('下一个tick执行');
});

执行顺序示例:

setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
// 输出顺序:nextTick → setTimeout → setImmediate

定时器的安全实践

  1. 验证延迟参数
function safeSetTimeout(callback, delay) {
  if (typeof delay !== 'number' || delay < 0) {
    delay = 0;
  }
  if (delay > 2147483647) { // 32位有符号整数最大值
    delay = 2147483647;
  }
  return setTimeout(callback, delay);
}
  1. 防止定时器泛滥
class TimerManager {
  constructor() {
    this.timers = new Set();
  }
  
  setTimeout(callback, delay) {
    const id = setTimeout(() => {
      this.timers.delete(id);
      callback();
    }, delay);
    this.timers.add(id);
    return id;
  }
  
  clearAll() {
    this.timers.forEach(id => clearTimeout(id));
    this.timers.clear();
  }
}
  1. 定时器降级策略
function adaptiveInterval(callback, idealInterval) {
  let actualInterval = idealInterval;
  let lastTime = Date.now();
  
  const tick = () => {
    const start = Date.now();
    callback();
    const duration = Date.now() - start;
    
    // 根据执行时间调整间隔
    if (duration > idealInterval * 0.5) {
      actualInterval = Math.max(idealInterval * 2, actualInterval);
    } else {
      actualInterval = idealInterval;
    }
    
    const elapsed = Date.now() - lastTime;
    const nextTick = Math.max(0, actualInterval - elapsed);
    setTimeout(tick, nextTick);
    lastTime = Date.now();
  };
  
  setTimeout(tick, idealInterval);
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:history对象控制

下一篇:函数基础

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌