定时器与延时器
定时器与延时器的基本概念
JavaScript提供了两种主要的定时机制:setTimeout
和setInterval
。setTimeout
用于在指定时间后执行一次回调函数,而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);
清除定时器的方法
使用clearTimeout
和clearInterval
可以分别取消setTimeout
和setInterval
设置的定时器。
const timeoutId = setTimeout(() => {
console.log('这段代码不会执行');
}, 5000);
// 3秒后取消定时器
setTimeout(() => {
clearTimeout(timeoutId);
}, 3000);
在组件卸载或页面离开时清除定时器是个好习惯:
// React组件示例
useEffect(() => {
const intervalId = setInterval(() => {
// 执行某些操作
}, 1000);
return () => {
clearInterval(intervalId);
};
}, []);
定时器的实际应用场景
- 延迟执行:用户停止输入后执行搜索
let searchTimeout;
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
performSearch(e.target.value);
}, 500);
});
- 轮询:定期检查服务器状态
function checkServerStatus() {
fetch('/api/status')
.then(response => response.json())
.then(data => {
updateStatus(data);
setTimeout(checkServerStatus, 5000);
});
}
checkServerStatus();
- 动画:简单的帧动画
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();
}
定时器的高级技巧
- 动态调整间隔:根据条件改变执行频率
let interval = 1000;
const adjustInterval = () => {
const id = setInterval(() => {
console.log(`当前间隔:${interval}ms`);
if (someCondition) {
clearInterval(id);
interval = 2000;
adjustInterval();
}
}, interval);
};
adjustInterval();
- 批量操作:避免频繁触发
const batchProcess = (() => {
let queue = [];
let isProcessing = false;
return (item) => {
queue.push(item);
if (!isProcessing) {
isProcessing = true;
setTimeout(() => {
processQueue(queue);
queue = [];
isProcessing = false;
}, 100);
}
};
})();
- 精确计时:补偿计时误差
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提供了几种定时器的替代方案:
- requestAnimationFrame:适合动画
function animate() {
// 动画逻辑
if (!shouldStop) {
requestAnimationFrame(animate);
}
}
animate();
- IntersectionObserver:元素可见时执行
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 元素进入视口时执行
}
});
});
observer.observe(document.querySelector('.target'));
- Web Workers:后台长时间运行任务
const worker = new Worker('worker.js');
worker.postMessage('开始工作');
worker.onmessage = (e) => {
console.log('收到消息:', e.data);
};
定时器的常见陷阱
- 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); // 正确
- 闭包问题:
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);
}
- 内存泄漏:
function startTimer() {
const data = getHugeData();
setInterval(() => {
process(data); // data不会被释放
}, 1000);
}
// 解决方案
function startTimer() {
setInterval(() => {
const data = getHugeData(); // 每次重新获取
process(data);
}, 1000);
}
定时器的调试技巧
- 给定时器命名:
const id = setTimeout(function timerHandler() {
// 在调试器中可以看到函数名
}, 1000);
- 记录定时器堆栈:
function createTraceableTimeout(callback, delay) {
const stack = new Error().stack;
return setTimeout(() => {
try {
callback();
} catch (e) {
console.error('定时器出错,创建时的堆栈:', stack);
throw e;
}
}, delay);
}
- 监控定时器数量:
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环境提供了额外的定时器功能:
- setImmediate:在当前事件循环结束时执行
setImmediate(() => {
console.log('立即执行');
});
- process.nextTick:在当前操作完成后执行
process.nextTick(() => {
console.log('下一个tick执行');
});
执行顺序示例:
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
// 输出顺序:nextTick → setTimeout → setImmediate
定时器的安全实践
- 验证延迟参数:
function safeSetTimeout(callback, delay) {
if (typeof delay !== 'number' || delay < 0) {
delay = 0;
}
if (delay > 2147483647) { // 32位有符号整数最大值
delay = 2147483647;
}
return setTimeout(callback, delay);
}
- 防止定时器泛滥:
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();
}
}
- 定时器降级策略:
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对象控制
下一篇:函数基础