您现在的位置是:网站首页 > setTimeout与setInterval文章详情
setTimeout与setInterval
陈川
【
JavaScript
】
27763人已围观
4780字
setTimeout
和setInterval
是JavaScript中用于控制代码执行时序的两个核心函数,它们通过延迟或周期性触发回调函数来实现异步任务调度。理解它们的差异、使用场景及潜在问题对编写高效、稳定的前端代码至关重要。
setTimeout的基本用法
setTimeout
用于在指定的延迟时间后执行一次回调函数。其基本语法为:
const timeoutID = setTimeout(callback, delay, arg1, arg2, ...);
- callback: 延迟结束后执行的函数
- delay: 延迟毫秒数(默认0)
- arg1, arg2...: 传递给回调函数的参数
典型示例:
setTimeout(() => {
console.log('3秒后执行');
}, 3000);
当需要取消尚未执行的延迟时,可以调用clearTimeout
:
const timer = setTimeout(() => {}, 1000);
clearTimeout(timer); // 取消定时器
setInterval的工作原理
setInterval
以固定时间间隔重复执行回调:
const intervalID = setInterval(callback, delay, arg1, arg2, ...);
示例实现每秒计数:
let count = 0;
const timer = setInterval(() => {
console.log(++count);
if (count >= 5) clearInterval(timer);
}, 1000);
执行机制差异
两者在事件循环中的表现不同:
- setTimeout:在调用后,经过指定延迟将回调推入任务队列
- setInterval:尝试严格按照时间间隔将回调推入队列
考虑以下代码:
let start = Date.now();
setTimeout(function run() {
console.log(`实际延迟:${Date.now() - start}ms`);
start = Date.now();
setTimeout(run, 100);
}, 100);
// 对比
setInterval(() => {
console.log(`间隔触发:${Date.now() - start}ms`);
}, 100);
潜在问题与解决方案
误差累积问题
setInterval
可能因回调执行时间超过间隔时间导致误差:
// 回调执行需要200ms,但间隔设为100ms
setInterval(() => {
const start = Date.now();
while(Date.now() - start < 200) {} // 阻塞200ms
console.log('执行完成');
}, 100);
解决方案:使用链式setTimeout
let timer = setTimeout(function tick() {
console.log('精确调度');
timer = setTimeout(tick, 100);
}, 100);
页面隐藏时的节流
浏览器对后台标签页的定时器有限制(通常≥1000ms):
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
console.log('页面隐藏,定时器被节流');
}
});
高级应用场景
实现动画帧率控制
const fps = 60;
const delay = 1000 / fps;
let lastTime = 0;
function animate(currentTime) {
if (currentTime - lastTime > delay) {
// 渲染逻辑
lastTime = currentTime;
}
requestAnimationFrame(animate);
}
实现超时重试机制
function fetchWithRetry(url, retries = 3, delay = 1000) {
return fetch(url).catch(err => {
return retries > 0
? new Promise(resolve =>
setTimeout(() =>
resolve(fetchWithRetry(url, retries - 1, delay * 2)),
delay)
)
: Promise.reject(err);
});
}
性能优化实践
批量DOM操作
const batchSize = 50;
let processed = 0;
const total = 1000;
function processBatch() {
const fragment = document.createDocumentFragment();
for (let i = 0; i < batchSize && processed < total; i++) {
const div = document.createElement('div');
fragment.appendChild(div);
processed++;
}
document.body.appendChild(fragment);
if (processed < total) {
setTimeout(processBatch, 0);
}
}
processBatch();
长任务分解
function processChunk(start, end) {
if (start >= end) return;
// 每次处理100条
const chunkEnd = Math.min(start + 100, end);
for (let i = start; i < chunkEnd; i++) {
// 数据处理逻辑
}
setTimeout(() => processChunk(chunkEnd, end), 0);
}
processChunk(0, 10000);
Node.js环境差异
在Node.js中,定时器属于Timers
相位:
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// 输出顺序可能不确定
使用unref()
防止定时器阻止进程退出:
const timer = setInterval(() => {}, 1000);
timer.unref(); // 程序可无需等待定时器退出
浏览器与Node.js的差异对比
特性 | 浏览器环境 | Node.js环境 |
---|---|---|
最小延迟 | 4ms(连续嵌套时) | 1ms |
后台标签页节流 | 是 | 否 |
优先级 | 普通任务队列 | Timers相位 |
最大延迟值 | 2^31-1 ms | 2^31-1 ms |
替代方案比较
requestAnimationFrame
更适合视觉动画:
function animate() {
// 动画逻辑
requestAnimationFrame(animate);
}
animate();
MessageChannel
实现零延迟调度:
const channel = new MessageChannel();
channel.port1.onmessage = () => {
console.log('立即执行');
};
channel.port2.postMessage('');
内存泄漏防范
未清理的定时器是常见泄漏源:
class Component {
constructor() {
this.timer = setInterval(this.update.bind(this), 1000);
}
update() { /* ... */ }
destroy() {
clearInterval(this.timer); // 必须手动清除
}
}
定时器调试技巧
Chrome DevTools中可查看活动定时器:
console.time('timer');
setTimeout(() => {
console.timeEnd('timer'); // 打印实际耗时
}, 1000);
特殊场景处理
零延迟的实际意义
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
// 输出顺序:promise → timeout
最大延迟限制
超过2147483647ms(约24.8天)将立即执行:
setTimeout(() => {
console.log('这个会立即执行');
}, 2 ** 31); // 超出最大延迟值