在JavaScript中,定时器是用于延迟执行代码或周期性执行代码的工具,核心有两个API:setTimeout(延迟执行一次)和setInterval(周期性重复执行),它们依赖JavaScript的单线程事件循环机制工作。
一、定时器的核心API
setTimeout:延迟指定时间后执行一次代码
- 作用:在指定的延迟时间后,执行一次回调函数(或代码片段)。
- 语法:
const timerId = setTimeout(callback, delay, arg1, arg2, …);
– `callback`:延迟后要执行的函数(必选)。
– `delay`:延迟时间(单位:毫秒,ms),默认值为0(可选)。
– `arg1, arg2…`:传递给`callback`的参数(可选)。
– 返回值:`timerId`(定时器ID,用于后续清除定时器)。
- 示例:
// 3秒后打印"延迟执行"
const timer = setTimeout((msg) => {
console.log(msg); // 输出:"延迟执行"
}, 3000, "延迟执行");
setInterval:每隔指定时间重复执行代码
- 作用:每隔delay毫秒,重复执行一次回调函数,直到被手动清除。
- 语法:
const timerId = setInterval(callback, delay, arg1, arg2, …);
– 参数含义与`setTimeout`一致。
– 返回值:`timerId`(用于清除定时器)。
- 示例:
// 每隔1秒打印当前时间
let count = 0;
const timer = setInterval(() => {
count++;
console.log(`第${count}次执行:${new Date().toLocaleTimeString()}`);
// 执行3次后停止
if (count >= 3) {
clearInterval(timer);
}
}, 1000);
清除定时器:clearTimeout与clearInterval
定时器创建后会一直存在(setTimeout执行后自动失效,setInterval会持续执行),若需提前终止,需用以下方法:
- clearTimeout(timerId):清除setTimeout创建的定时器。
- clearInterval(timerId):清除setInterval创建的定时器。
示例:提前清除setTimeout
const timer = setTimeout(() => {
console.log("这段代码不会执行");
}, 2000);
// 1秒后清除定时器
setTimeout(() => {
clearTimeout(timer);
console.log("定时器已被清除");
}, 1000);
二、关键特性与注意事项
延迟时间并非“精确执行时间”
- delay是“最小延迟时间”,而非“精确执行时间”。因为JavaScript是单线程的,若主线程被其他任务(如同步代码、其他回调)阻塞,定时器回调会被放入任务队列等待,实际执行时间会比delay长。 示例:
// 同步代码阻塞主线程5秒
console.log("开始");
const start = Date.now();
while (Date.now() – start < 5000) {} // 阻塞5秒
// 虽然设置了1秒延迟,但实际会在5秒后(主线程空闲时)执行
setTimeout(() => {
console.log("执行了"); // 5秒后才输出
}, 1000);
定时器回调中的this指向
- 普通函数作为回调时,this默认指向全局对象(浏览器中为window,严格模式下为undefined)。
- 箭头函数作为回调时,this继承外层作用域的this(与定义时的上下文一致)。 示例:
const obj = {
name: "测试",
func1: function() {
// 普通函数回调:this指向window
setTimeout(function() {
console.log(this.name); // 输出:undefined(window无name属性)
}, 100);
// 箭头函数回调:this继承自func1的this(即obj)
setTimeout(() => {
console.log(this.name); // 输出:"测试"
}, 100);
}
};
obj.func1();
setInterval的“累积执行”风险
setInterval会严格按照delay间隔安排下一次执行,若前一次回调执行时间超过delay(如回调内有耗时操作),会导致多个回调堆积,在主线程空闲时连续执行。
解决办法:用setTimeout嵌套调用替代setInterval,确保前一次执行完成后再安排下一次:
// 嵌套setTimeout:避免累积执行
function repeatTask() {
console.log("执行任务");
// 前一次执行完后,再延迟1秒安排下一次
setTimeout(repeatTask, 1000);
}
repeatTask();
延迟时间为0的特殊作用
setTimeout(callback, 0)不会立即执行,而是将回调放入“宏任务队列”,等待主线程同步代码执行完毕后立即执行。常用于:
- 将耗时操作推迟到主线程空闲时执行,避免阻塞UI渲染。
- 调整代码执行顺序(让异步代码在同步代码后执行)。
示例:
console.log("同步代码1");
setTimeout(() => {
console.log("延迟0ms的代码"); // 最后执行(在同步代码后)
}, 0);
console.log("同步代码2");
// 输出顺序:同步代码1 → 同步代码2 → 延迟0ms的代码
三、定时器的应用场景
延迟执行:单次延迟后触发操作(setTimeout 为主)
页面加载后的非关键资源初始化
页面核心内容(如首屏DOM、关键样式)加载完成后,延迟初始化非紧急资源(如广告、统计脚本、非首屏组件),避免阻塞主线程,提升首屏加载速度。 (优化用户体验)
// 页面加载完成后,延迟2秒初始化非关键广告组件
window.addEventListener('load', () => {
setTimeout(() => {
initAdComponent(); // 初始化广告
initAnalytics(); // 初始化统计
}, 2000);
});
用户操作后的延迟反馈
用户触发操作(如点击、输入)后,延迟执行提示或后续操作,提升交互体验。例如:
- 按钮点击后延迟禁用(避免快速重复点击);
- 输入框内容变化后,延迟验证(减少频繁校验的性能消耗)。
// 按钮点击后延迟1秒恢复可点击状态(防止重复提交)
const submitBtn = document.getElementById('submit');
submitBtn.onclick = function() {
this.disabled = true; // 立即禁用
setTimeout(() => {
this.disabled = false; // 1秒后恢复
}, 1000);
};
模拟异步操作(测试/调试)
开发中若需模拟后端API的延迟响应(如接口请求耗时),可用 setTimeout 模拟异步等待,无需依赖真实接口。
// 模拟接口请求(延迟1.5秒返回数据)
function mockApiRequest() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ code: 200, data: '模拟数据' });
}, 1500);
});
}
// 使用:像调用真实接口一样使用
mockApiRequest().then(res => console.log(res));
周期性操作:重复执行某任务(setInterval 或嵌套 setTimeout)
实时数据刷新
需定期从后端拉取最新数据的场景(如实时监控面板、聊天消息列表、股票行情),通过定时器周期性请求接口。(轮询)
// 每隔30秒刷新一次用户在线状态
let statusTimer;
function startRefreshStatus() {
statusTimer = setInterval(async () => {
const res = await fetch('/api/user/status');
const status = await res.json();
updateStatusUI(status); // 更新UI显示
}, 30000); // 30秒一次
}
// 页面离开时停止刷新(避免无效请求)
window.addEventListener('beforeunload', () => {
clearInterval(statusTimer);
});
倒计时/计时器
秒杀活动、考试时间、订单超时等场景,需实时显示剩余时间,通过定时器每秒更新一次时间戳。
// 倒计时:从10秒开始递减
let countdown = 10;
const countdownEl = document.getElementById('countdown');
const timer = setInterval(() => {
countdown—;
countdownEl.innerText = `剩余${countdown}秒`;
if (countdown <= 0) {
clearInterval(timer);
countdownEl.innerText = '时间到!';
}
}, 1000);
轮播图/幻灯片自动切换
轮播组件需要每隔固定时间自动切换图片,用定时器控制切换逻辑,同时支持用户交互(如点击、hover)时暂停/恢复。
const carousel = {
currentIndex: 0,
timer: null,
// 启动轮播(每隔5秒切换一张)
start() {
this.timer = setInterval(() => {
this.currentIndex = (this.currentIndex + 1) % 5; // 假设5张图
this.switchTo(this.currentIndex); // 切换到当前索引图片
}, 5000);
},
// 暂停轮播(用户hover时)
pause() {
clearInterval(this.timer);
}
};
// 初始化轮播
carousel.start();
// 鼠标悬停时暂停,离开时恢复
document.querySelector('.carousel').addEventListener('mouseenter', () => {
carousel.pause();
});
document.querySelector('.carousel').addEventListener('mouseleave', () => {
carousel.start();
});
优化交互体验:控制操作频率
防抖(Debounce):延迟执行高频操作
对于高频触发的事件(如输入框输入、窗口resize、滚动),通过 setTimeout 延迟执行,避免频繁触发导致的性能问题。(性能优化)例如:
- 搜索输入框:用户停止输入后才触发搜索请求;
- 窗口调整:用户停止拖拽窗口后才重新计算布局。
// 搜索输入框防抖:停止输入1秒后才请求接口
let searchTimer;
const searchInput = document.getElementById('search');
searchInput.oninput = function(e) {
clearTimeout(searchTimer); // 每次输入都清除上一次的定时器
searchTimer = setTimeout(() => {
fetch(`/api/search?keyword=${e.target.value}`); // 执行搜索
}, 1000); // 1秒内无输入则触发
};
自动保存(如表单、编辑器)
在线文档、表单编辑时,每隔一段时间自动保存内容,避免用户因意外(如刷新、关闭)丢失数据。(数据安全)
// 编辑器每30秒自动保存
let saveTimer;
function startAutoSave() {
saveTimer = setInterval(() => {
const content = editor.getValue(); // 获取编辑器内容
localStorage.setItem('draft', content); // 保存到本地存储
showSaveTip(); // 显示“已自动保存”提示
}, 30000);
}
// 页面关闭前清除定时器
window.addEventListener('beforeunload', () => {
clearInterval(saveTimer);
});
动画与过渡效果
简单的数值变化动画(如数字滚动、进度条加载)可通过定时器实现(复杂动画更推荐 requestAnimationFrame,但定时器适合轻量化场景)。 (优化交互体验)
// 进度条从0%动画到100%(1秒完成)
const progressBar = document.getElementById('progress');
let progress = 0;
const timer = setInterval(() => {
progress += 1;
progressBar.style.width = `${progress}%`;
if (progress >= 100) {
clearInterval(timer);
}
}, 10); // 每10ms更新一次,1秒内完成100步
注意事项(避坑指南)
// 安全的周期性执行(前一次完成后再延迟)
function safeRepeat() {
doHeavyTask(); // 可能耗时的任务
setTimeout(safeRepeat, 1000); // 任务完成后再延迟1秒
}
safeRepeat();
const obj = {
name: '测试',
start() {
// 箭头函数继承obj的this
setTimeout(() => {
console.log(this.name); // 正确输出“测试”
}, 1000);
}
};
四、防抖与节流
节流(Throttle)和防抖(Debounce)是前端开发中用于控制高频事件触发频率的两种优化技术,核心区别在于执行时机和触发规则:
- 防抖(Debounce):事件触发后,等待一段时间再执行函数;如果在这段时间内事件再次触发,则重新计时,直到最后一次触发后等待时间结束才执行。
- 节流(Throttle):事件触发后,立即执行函数,然后在接下来的一段时间内(冷却期),无论事件触发多少次,都不再执行,直到冷却期结束后才能再次执行。
防抖(Debounce)示例:搜索输入框
场景:用户在搜索框输入时,不需要每次输入都立即请求接口(如输入“苹果”,可能触发“苹”“苹果”多次请求),而是等用户停止输入1秒后再请求,减少无效请求。
代码实现:
function debounce(fn, delay) {
let timer = null; // 保存定时器ID
return function(…args) {
// 每次触发时,清除上一次的定时器(重新计时)
clearTimeout(timer);
// 重新设置定时器,延迟delay后执行函数
timer = setTimeout(() => {
fn.apply(this, args); // 执行目标函数
}, delay);
};
}
// 模拟搜索请求函数
function search(keyword) {
console.log(`搜索:${keyword}`);
}
// 为输入框绑定防抖处理后的搜索函数(延迟1000ms)
const input = document.getElementById('search-input');
input.oninput = debounce((e) => {
search(e.target.value);
}, 1000);
效果:
- 用户快速输入“苹→苹果”,整个过程中输入间隔小于1秒,只会在最后一次输入(“苹果”)后等待1秒,执行一次search("苹果")。
- 如果用户输入“苹”后停顿2秒,会执行search("苹")。
节流(Throttle)示例:滚动加载数据
场景:用户滚动页面时,需要监听滚动位置(如判断是否到达底部加载更多),但不需要每次滚动都触发(可能1秒内触发几十次),而是每隔500ms触发一次,避免频繁计算。
代码实现:
function throttle(fn, interval) {
let lastTime = 0; // 记录上一次执行的时间
return function(…args) {
const now = Date.now(); // 当前时间戳
// 如果当前时间 – 上一次执行时间 >= 间隔时间,则执行
if (now – lastTime >= interval) {
fn.apply(this, args); // 执行目标函数
lastTime = now; // 更新上一次执行时间
}
};
}
// 模拟滚动位置检查函数
function checkScroll() {
const scrollTop = document.documentElement.scrollTop;
console.log(`滚动位置:${scrollTop}px`);
}
// 为窗口绑定节流处理后的滚动函数(间隔500ms)
window.onscroll = throttle(checkScroll, 500);
效果:
- 用户快速滚动页面,1秒内可能触发10次滚动事件,但checkScroll只会在第0ms、500ms、1000ms时执行,即每500ms执行一次。
核心区别对比
执行时机 | 事件触发后等待一段时间,最后一次触发后执行 | 事件触发后立即执行,之后冷却期内不执行 |
触发规则 | 多次触发会重置计时,延迟执行 | 多次触发不会重置计时,固定间隔执行 |
核心目的 | 等待“操作结束”后执行一次 | 控制“持续操作”的执行频率 |
典型场景 | 搜索输入、窗口resize、按钮防重复点击 | 滚动加载、拖拽尺寸计算、高频点击限制 |
总结
- 防抖像“电梯”:按下按钮后,电梯会等10秒关门;如果有人再按,重新等10秒,直到没人按才关门。
- 节流像“红绿灯”:红灯亮起后,无论多少车来,都要等30秒绿灯才放行,固定间隔执行。
根据场景选择:需要“等待操作结束”用防抖,需要“控制执行频率”用节流。
评论前必须登录!
注册