1. setTimeout 基础用法
setTimeout
是 JavaScript 提供的异步定时器函数,用于在指定时间后执行回调函数。
1.1 基本语法
setTimeout(callback, delay, arg1, arg2, ...)
callback
:要执行的函数delay
:延迟时间(毫秒),默认0
arg1, arg2, ...
:可选参数,传递给回调函数
1.2 示例
// 基本用法
setTimeout(() => {
console.log("1秒后执行");
}, 1000);
// 带参数
setTimeout((name, age) => {
console.log(`姓名:${name},年龄:${age}`);
}, 1000, "张三", 25);
1.3 返回值
setTimeout
返回一个 timeoutID
,可用于 clearTimeout
取消执行:
const timerId = setTimeout(() => {
console.log("这段代码不会执行");
}, 1000);
clearTimeout(timerId); // 取消定时器
2. setTimeout 的 this 指向问题
由于 JavaScript 的 this
绑定规则,setTimeout
的回调函数可能丢失 this
绑定。
2.1 问题示例
const obj = {
name: "张三",
sayName() {
console.log(this.name);
}
};
obj.sayName(); // "张三"(正确)
setTimeout(obj.sayName, 1000); // undefined(this 指向 window/global)
原因:
obj.sayName
作为回调传入setTimeout
时,相当于:const func = obj.sayName; setTimeout(func, 1000); // this 丢失
2.2 解决方法
(1) 使用箭头函数(推荐)
setTimeout(() => {
obj.sayName(); // this 正确指向 obj
}, 1000);
(2) 使用 bind
setTimeout(obj.sayName.bind(obj), 1000);
(3) 使用匿名函数
setTimeout(function() {
obj.sayName();
}, 1000);
3. setTimeout 的延迟问题
setTimeout
的延迟时间并非绝对精确,可能受以下因素影响:
3.1 最小延迟限制
- 浏览器:嵌套
setTimeout
超过 5 层后,最小延迟为 4ms。 - Node.js:无此限制。
示例:嵌套 setTimeout 的最小延迟
let start = Date.now();
let count = 0;
function test() {
if (count++ < 10) {
setTimeout(test, 0);
} else {
console.log(`实际耗时:${Date.now() - start}ms`);
}
}
test();
// 输出示例:实际耗时:45ms(而不是 0ms)
3.2 任务队列机制
JavaScript 是单线程的,setTimeout
的回调必须等待当前执行栈清空才能执行:
console.log("开始");
setTimeout(() => {
console.log("setTimeout 回调");
}, 0);
console.log("结束");
// 输出顺序:
// 开始
// 结束
// setTimeout 回调
4. setTimeout 的实际应用
4.1 防抖(Debounce)
防止高频触发(如输入框搜索):
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
const searchInput = document.getElementById("search");
const debouncedSearch = debounce(function() {
console.log("搜索:", this.value);
}, 300);
searchInput.addEventListener("input", debouncedSearch);
4.2 任务分片(Chunking)
避免长时间阻塞主线程:将大数组分成若干小块,每次只处理一块,并在每块处理后将控制权交还给浏览器,使得浏览器能够更新 UI 或响应用户输入,保证程序不会冻结或卡顿。
function processLargeArray(array, chunkSize, callback, done) {
let index = 0;
function nextChunk() {
const chunkEnd = Math.min(index + chunkSize, array.length);
for (; index < chunkEnd; index++) {
callback(array[index], index);
}
if (index < array.length) {
// 每处理完一块数据后,通过 setTimeout 将控制权交还给浏览器,
// 使浏览器能够处理 UI 更新(比如重绘页面、响应用户事件等)。
// setTimeout 会在当前事件循环的空闲时段执行 nextChunk,从而不会阻塞 UI 渲染。
setTimeout(nextChunk, 0); // 让浏览器处理 UI 更新
} else {
done?.();
}
}
nextChunk();
}
// 使用示例
const bigArray = new Array(10000).fill().map((_, i) => i);
processLargeArray(
bigArray,
100,
(item) => console.log(item),
() => console.log("处理完成")
);
4.3 动画循环
结合 requestAnimationFrame
实现流畅动画:
// 用js模拟一个元素1秒移动300px
function animate(element, duration) {
const start = performance.now();
function step(timestamp) {
const progress = (timestamp - start) / duration;
if (progress < 1) {
element.style.transform = `translateX(${progress * 300}px)`;
setTimeout(() => requestAnimationFrame(step), 16); // 模拟每秒 60 帧的效果,1000ms/60 = 16.67。
}
}
requestAnimationFrame(step);
}
animate(document.querySelector(".box"), 1000); // 1秒动画
5. 总结
问题 | 原因 | 解决方案 |
---|---|---|
this 丢失 | 回调函数独立调用 | 使用 bind 或箭头函数 |
延迟不精确 | 任务队列机制、最小延迟 | 改用 requestAnimationFrame |
嵌套延迟 | 浏览器 4ms 限制 | 减少嵌套或改用 setImmediate (Node.js) |
标签页降频 | 浏览器优化策略 | 改用 Web Worker 或 requestIdleCallback |
最佳实践:
- 避免深层嵌套
setTimeout
。 - 需要精确计时时,使用
performance.now()
计算实际延迟。 - 动画推荐
requestAnimationFrame
,IO 操作推荐Promise
+setTimeout
。