云计算百科
云计算领域专业知识百科平台

JavaScript手录16-定时器

画板

在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步

注意事项(避坑指南)

  • 及时清除定时器:不再需要的定时器必须用 clearTimeout/clearInterval 清除,否则会导致内存泄漏(尤其是单页应用中组件销毁时)。
  • 避免 setInterval 累积执行:若回调函数执行时间超过 delay(如耗时操作),setInterval 会导致多个回调堆积。此时推荐用 **嵌套 **setTimeout 替代,确保前一次执行完成后再安排下一次:
  • // 安全的周期性执行(前一次完成后再延迟)
    function safeRepeat() {
    doHeavyTask(); // 可能耗时的任务
    setTimeout(safeRepeat, 1000); // 任务完成后再延迟1秒
    }
    safeRepeat();

  • 处理 this 指向:定时器回调中的普通函数 this 指向全局对象(window),需用箭头函数或 bind 绑定正确的上下文:
  • 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执行一次。

    核心区别对比

    特性防抖(Debounce)节流(Throttle)
    执行时机 事件触发后等待一段时间,最后一次触发后执行 事件触发后立即执行,之后冷却期内不执行
    触发规则 多次触发会重置计时,延迟执行 多次触发不会重置计时,固定间隔执行
    核心目的 等待“操作结束”后执行一次 控制“持续操作”的执行频率
    典型场景 搜索输入、窗口resize、按钮防重复点击 滚动加载、拖拽尺寸计算、高频点击限制

    总结

    • 防抖像“电梯”:按下按钮后,电梯会等10秒关门;如果有人再按,重新等10秒,直到没人按才关门。
    • 节流像“红绿灯”:红灯亮起后,无论多少车来,都要等30秒绿灯才放行,固定间隔执行。

    根据场景选择:需要“等待操作结束”用防抖,需要“控制执行频率”用节流。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » JavaScript手录16-定时器
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!