JavaScript 异步编程:setTimeout 与 setInterval
JavaScript 是单线程语言,但通过异步机制可以实现“同时”处理多个任务。setTimeout 和 setInterval 是最基础、最常用的异步定时器函数,它们让你能在未来某个时间点执行代码,而不阻塞当前程序运行。
理解 setTimeout:延迟执行一次
使用 setTimeout 安排一段代码在指定毫秒数后执行一次。
基本语法:
const timerId = setTimeout(callback, delay, ...args);
callback:要执行的函数。delay:延迟时间(单位:毫秒)。1000表示 1 秒。...args(可选):传递给回调函数的参数。- 返回值
timerId是一个数字标识符,可用于取消该定时器。
示例:3 秒后弹出提示
setTimeout(() => {
console.log("3 秒过去了!");
}, 3000);
注意:实际延迟可能略长于设定值。浏览器或 Node.js 会根据事件循环状态调度任务,不能保证精确到毫秒。
取消 setTimeout
调用 clearTimeout(timerId) 可阻止尚未执行的定时器。
const id = setTimeout(() => {
console.log("这条消息不会出现");
}, 5000);
// 在 2 秒后取消
setTimeout(() => {
clearTimeout(id);
console.log("已取消定时器");
}, 2000);
理解 setInterval:周期性重复执行
使用 setInterval 每隔指定毫秒数重复执行一段代码。
基本语法:
const intervalId = setInterval(callback, interval, ...args);
interval:两次执行之间的间隔时间(毫秒)。- 其他参数与
setTimeout相同。
示例:每秒打印当前时间
const clock = setInterval(() => {
console.log(new Date().toLocaleTimeString());
}, 1000);
取消 setInterval
调用 clearInterval(intervalId) 停止周期性执行。
let count = 0;
const counter = setInterval(() => {
count++;
console.log(`第 ${count} 次执行`);
if (count >= 5) {
clearInterval(counter);
console.log("计数结束");
}
}, 1000);
```
---
## 关键区别与使用场景
| 特性 | `setTimeout` | `setInterval` |
| :--- | :---: | :---: |
| 执行次数 | **仅一次** | **无限重复**(除非手动清除) |
| 时间起点 | 从调用时刻开始计时 | 每次回调结束后立即开始下一次计时(存在累积误差) |
| 适用场景 | 延迟加载、防抖、一次性提醒 | 轮询、动画帧、实时更新(如倒计时、心跳检测) |
**重要提醒**:`setInterval` 的间隔是“理想间隔”,如果回调函数执行时间超过设定间隔,下一次回调会立即执行(无等待),导致实际间隔变短。对于高精度定时任务,建议用 `setTimeout` 递归实现。
### 用 setTimeout 模拟 setInterval(推荐高精度场景)
```javascript
function repeatWithTimeout() {
console.log("执行任务");
// 执行完后再安排下一次
setTimeout(repeatWithTimeout, 1000);
}
repeatWithTimeout(); // 启动
```
这种方式确保每次执行完成后才开始计时下一次,避免任务堆积。
---
## 常见陷阱与最佳实践
1. **不要传递字符串作为回调**
❌ 错误写法:`setTimeout("console.log('bad')", 1000)`
✅ 正确写法:`setTimeout(() => console.log('good'), 1000)`
2. **及时清理不再需要的定时器**
在网页组件卸载(如 React 的 `useEffect` 清理函数)或长时间运行的服务中,**务必调用 `clearTimeout` 或 `clearInterval`**,否则会造成内存泄漏或意外行为。
3. **最小延迟限制**
浏览器对嵌套层级较深的 `setTimeout`/`setInterval` 会强制设置最小延迟为 `4ms`(HTML5 规范)。例如连续快速调用时,即使设为 `0`,实际也会延迟约 4 毫秒。
4. **页面隐藏时的行为差异**
当用户切换到其他标签页时,部分浏览器会**降低 `setInterval` 的执行频率**(如 Chrome 限制为每秒最多 1 次),以节省资源。若需精确计时(如游戏、音频同步),应结合 `requestAnimationFrame` 或使用 Web Workers。
---
## 实战:实现一个倒计时器
**创建一个从 10 秒倒数到 0 的控制台倒计时,并在结束时停止**。
```javascript
let remaining = 10;
const countdown = setInterval(() => {
console.log(`倒计时: ${remaining} 秒`);
remaining--;
if (remaining < 0) {
clearInterval(countdown);
console.log("时间到!");
}
}, 1000);
此代码每秒输出剩余时间,当计数小于 0 时自动清除定时器,防止无限运行。
异步执行顺序验证
理解事件循环如何处理定时器:
console.log("开始");
setTimeout(() => {
console.log("setTimeout 0ms");
}, 0);
Promise.resolve().then(() => {
console.log("Promise 回调");
});
console.log("结束");
输出顺序为:
开始
结束
Promise 回调
setTimeout 0ms
因为 Promise.then 属于微任务(microtask),会在当前宏任务结束后立即执行;而 setTimeout 属于宏任务(macrotask),需等待下一次事件循环。这说明定时器回调总是在当前同步代码和所有微任务执行完毕后才运行。
// 防抖示例:用户停止输入 500ms 后才发送请求
let debounceTimer;
function handleInput(value) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
console.log("发送搜索请求:", value);
}, 500);
}
暂无评论,快来抢沙发吧!