广告

前端干货 | JavaScript定时器用法与实战代码示例

1. 基本概念与常用 API

1.1 setTimeout 的基本用法

在前端开发中,setTimeout 是最常用的定时执行 API。它允许在指定的毫秒延迟后执行一个回调函数,从而实现简单的等待、延迟执行以及后续动作的调度。通过返回一个唯一的定时器 ID,你还可以在需要时使用 clearTimeout 来取消尚未执行的回调。

理解底层时序很重要:setTimeout 的回调会被放入事件循环的宏任务队列,在当前任务完成后才会执行,且实际执行时间可能因为浏览器繁忙而略有偏差。因此在设计 UI 响应时,需要考虑到这一点。

// 简单的延时执行
setTimeout(() => {console.log('延迟 1s 执行')
}, 1000);// 取消定时任务
const t = setTimeout(() => {console.log('不会执行')
}, 2000);
clearTimeout(t);

1.2 setInterval 与清理

setInterval 用于在固定间隔时间重复执行,直到被 clearInterval 取消。它非常适合轮询式更新、进度条刷新或周期性数据拉取的场景,但需要注意回调体积过大时可能与渲染形成竞争。

与 setTimeout 不同的是,setInterval 会在每一次周期结束后重新调度下一次执行,因此如果回调中存在较重的工作,可能会导致下一个周期的延迟,甚至堆积调用。

// 每秒输出一次
const intervalId = setInterval(() => {console.log('每秒一次')
}, 1000);// 停止间隔执行
clearInterval(intervalId);

1.3 事件循环与定时任务

浏览器的执行模型基于事件循环:宏任务队列和微任务队列共同协作。setTimeoutsetInterval 属于宏任务,当当前任务栈为空时,事件循环会从宏任务队列中取出一个执行回调。理解这个机制有助于避免回调竞争与 UI 卡顿。

在实际场景中,若回调执行时间较长,建议拆分成更小的工作单元,或结合 requestAnimationFrame 将复杂绘制放到下一个浏览器绘制周期内完成,以保持流畅。

2. 实战场景:防抖、节流与动画调度

2.1 防抖(Debounce)实现

防抖是在事件连续触发后,等待一段时间才执行最终回调。常用于输入框自动完成、搜索提示等场景,可以避免多次请求导致的性能问题。实现上通常借助 setTimeout,在新的触发到来时清空上一次定时器。

重要的是要记住,最后一次触发才执行,所以在界面提示上应给出明确的反馈。

function debounce(fn, delay) {let timer = null;return function(...args) {const context = this;clearTimeout(timer);timer = setTimeout(() => fn.apply(context, args), delay);};
}// 使用示例
const inputHandler = debounce(() => {console.log('发送搜索请求');
}, 300);

2.2 节流(Throttle)实现

节流保证在一定时间内只执行一次回调,避免高频触发带来的性能压力。常用于滚动监听、窗口大小调整等场景。与 debounce 不同,节流会让回调在固定时间段内至少执行一次。

通过 setTimeout 和一个时间阈值来实现简单版本的节流,或者使用时间戳来控制上一次执行的时间。

function throttle(fn, limit) {let last = 0;return function(...args) {const now = Date.now();if (now - last >= limit) {last = now;fn.apply(this, args);}};
}// 使用示例
window.addEventListener('scroll', throttle(() => {console.log('滚动节流执行');
}, 200));

2.3 动画与渲染调度:requestAnimationFrame 的对比

在涉及平滑动画或实时渲染时,requestAnimationFrame 比 setInterval 更优。rAF 会在浏览器准备绘制帧时触发回调,提供更稳健的时间参数和省电优化。它避免了与重绘频率硬性绑定导致的掉帧问题。

如果需要执行逐帧更新,应优先使用 requestAnimationFrame,并在回调中进行渐变、位移或绘制计算。对于需要固定时间步长的运动,可结合时间戳进行插值。

let start;
function loop(ts) {if (start === undefined) start = ts;const elapsed = ts - start;// 基于 elapsed 计算动画状态// e.g., 位置随时间线性变化// renderFrame(elapsed);requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

3. 实战:Promise、超时与异步请求的定时组合

3.1 将定时器用于 Promise 超时控制

在需要对异步请求设定超时限制时,可以组合定时器与 Promise。通过 Promise.race,将网络请求与定时器并行比较,先返回的结果即为最终结果。

关键点在于 取消未完成的请求或清理定时器,以避免内存泄漏和不必要的网络开销。

function timeoutPromise(promise, ms) {let timer;const timeout = new Promise((_, reject) => {timer = setTimeout(() => reject(new Error('timeout')), ms);});return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
}// 使用示例
const fetchData = fetch('/api/data').then(res => res.json());
timeoutPromise(fetchData, 5000).then(data => console.log('获取到数据', data)).catch(err => console.error(err));

3.2 将定时器与 Abort 控制结合的取消策略

在涉及大量异步请求的页面,可以结合 AbortController 来取消未完成的请求,避免用户快速跳转时的资源浪费。定时器也应在需要时清理,确保页面生命周期内没有悬空任务。

下面的示例展示了在页面卸载时清理所有定时器的做法,避免后台继续执行。

let timerId;
function startPeriodicWork() {timerId = setInterval(() => {console.log('周期性工作中');}, 1000);
}
function stopPeriodicWork() {clearInterval(timerId);
}
window.addEventListener('beforeunload', stopPeriodicWork);// 使用 AbortController 取消网络请求
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal }).then(r => r.json()).then(console.log).catch(e => console.error('请求被取消', e));// 取消示例
controller.abort();

4. 容错与性能优化技巧

4.1 合理设置延迟与异步工作粒度

在实际开发中,避免把所有任务塞进一个定时器回调里。应将复杂逻辑拆分成小任务,并尽量将 UI 更新放在最前端,耗时计算放在浏览器空闲时段执行,提升页面响应性。

要点在于:按任务粒度拆分、把高成本操作放到低优先级队列中执行,以及在必要时使用 requestIdleCallback 来的空闲时间执行。

4.2 组件生命周期中的清理策略

在前端框架中,组件卸载时应清理所有挂起的定时器,以避免内存泄漏和非法回调继续执行。将定时器句柄保存在组件实例中,并在 onUnmount/组件销毁 时统一清理。

前端干货 | JavaScript定时器用法与实战代码示例

示例中的清理模式可以确保应用在复杂页面切换时保持稳定。

// 伪组件清理示例
class Widget {constructor() {this.timer = null;}mount() {this.timer = setInterval(() => console.log('正在工作'), 1000);}unmount() {if (this.timer) clearInterval(this.timer);}
}

4.3 与 UI 渲染的协同优化

将定时任务与渲染节奏分离,可以显著提升用户体验。优先在定时器里处理轻量逻辑,重绘相关的 UI 更新尽量放到最前,避免阻塞主线程。节流与防抖的组合应用也能在高频事件中维持稳定帧率。

如果涉及大量 DOM 更新,建议采用虚拟列表、请求友好地分批渲染,配合定时器控制渲染节奏,确保 at 最少的重绘开销。

广告