Skip to content

D16. 防抖、节流与缓存

6.1 频繁事件的监听执行须加以控制

目标:理解事件高频触发对性能的影响及控制必要性。

6.1.1 问题场景

  • 页面滚动事件:滚动时频繁触发可能导致页面卡顿。
  • 输入框搜索建议:用户输入时每键入一个字符即发起请求。
  • 窗口大小调整:频繁触发重新渲染布局。

性能影响

  • CPU占用过高:连续触发函数导致主线程阻塞。
  • 网络资源浪费:重复请求API或计算冗余数据。

6.2 防抖与节流:限制事件频率的核心策略

目标:掌握防抖与节流的实现原理及适用场景。

6.2.1 防抖(Debounce)

原理:延迟执行函数,若在延迟期内再次触发,则重置计时器。

javascript
function debounce(func, delay) {
    let timer;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => func.apply(this, args), delay);
    };
}

// 应用场景:搜索框输入后1秒触发搜索
const input = document.querySelector('input');
input.addEventListener('input', debounce(fetchSearchResults, 1000));

特性

  • 触发时机:最后一次操作后延迟执行。
  • 适用场景:输入验证、窗口调整、滚动加载。
  • 优化点:可通过添加immediate参数实现首次立即执行(如表单提交)。

6.2.2 节流(Throttle)

原理:强制函数在指定时间内仅执行一次

javascript
function throttle(func, delay) {
    let lastExec = 0;
    return function(...args) {
        const now = Date.now();
        if (now - lastExec >= delay) {
            func.apply(this, args);
            lastExec = now;
        }
    };
}

// 应用场景:滚动事件优化
window.addEventListener('scroll', throttle(loadMoreItems, 200));

特性

  • 触发时机:首次触发立即执行,后续按固定时间间隔执行。
  • 适用场景:滚动事件、鼠标移动、连续点击。
  • 优化点:结合定时器实现“时间戳+间隔”的精准控制。

6.2.3 防抖与节流对比

特性防抖(Debounce)节流(Throttle)
执行时机最后一次操作后延迟执行固定时间间隔内执行一次
适用场景输入验证、窗口调整滚动事件、连续点击
首次触发可延迟立即执行
最后一次保证执行可能不执行(需优化)

6.3 缓存:减少重复计算与网络请求

目标:通过缓存降低耗时操作的重复执行。

6.3.1 缓存的核心思想

定义:将已计算或获取的数据暂存起来,后续直接读取缓存。 优点

  • 提升性能:减少重复计算或网络请求(如浏览器缓存图片)。
  • 降低资源消耗:减少服务器压力(如缓存API响应)。 缺点
  • 存储占用:缓存数据过多可能导致内存泄漏。
  • 数据过期风险:缓存数据可能与真实数据不一致。

6.3.2 生活案例:手机缓存的优缺点

  • 优点
    • 加速访问:应用启动时加载本地缓存数据(如微信聊天记录)。
    • 离线可用:已缓存的网页或视频可离线查看。
  • 缺点
    • 占用存储空间:过多缓存可能导致手机存储不足。
    • 数据滞后:缓存未更新时可能显示旧信息(如天气应用)。

6.3.3 缓存策略:LRU(最近最少使用)

原理:淘汰最久未被访问的数据。

javascript
class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.cache = new Map(); // 映射表存储键值对
    }
    get(key) {
        if (this.cache.has(key)) {
            const val = this.cache.get(key);
            this.cache.delete(key); // 移动到末尾
            this.cache.set(key, val);
            return val;
        }
        return -1;
    }
    put(key, value) {
        if (this.cache.has(key)) {
            this.cache.delete(key);
        }
        this.cache.set(key, value);
        if (this.cache.size > this.capacity) {
            // 删除最久未使用的(Map的迭代顺序是插入顺序)
            const oldest = this.cache.keys().next().value;
            this.cache.delete(oldest);
        }
    }
}

// 应用场景:缓存API请求结果
const cache = new LRUCache(3);
cache.put('user1', { name: 'Alice' });
cache.put('user2', { name: 'Bob' });
console.log(cache.get('user1')); // 快速获取,且更新为最近使用

6.4 映射表是缓存的基础

目标:通过Map实现带缓存的函数或API请求。

6.4.1 基于Map的缓存实现

javascript
// 带缓存的API请求示例
const apiCache = new Map();

async function fetchDataWithCache(url) {
    if (apiCache.has(url)) {
        console.log('使用缓存数据');
        return apiCache.get(url);
    }
    const response = await fetch(url);
    const data = await response.json();
    apiCache.set(url, data); // 缓存结果
    return data;
}

// 清除缓存(如页面卸载时)
function clearCache() {
    apiCache.clear();
}

特性

  • 自动管理键值对:通过Maphasgetset方法操作缓存。
  • 生命周期控制:可通过clear或LRU策略管理缓存大小。

知识回顾

  1. 防抖与节流:通过延迟或间隔执行控制事件频率,解决性能问题。
  2. 缓存:通过存储计算结果或数据副本提升效率,需权衡存储与一致性。
  3. 映射表(Map):提供高效的键值对存储,是实现LRU缓存的核心数据结构。

课后练习

  1. 选择题:以下哪种场景更适合使用防抖而非节流?

    • A. 用户滚动页面时加载新内容
    • B. 搜索框输入时实时搜索建议
    • C. 按钮快速连续点击
  2. 代码题:实现一个支持maxAge(缓存有效期)的LRU缓存类。

  3. 分析题:解释为什么浏览器需要定期清理缓存,如何平衡缓存效率与存储占用?

扩展阅读

  • 防抖节流进阶
    • 移动端适配:触摸事件的防抖优化(如touchmove)。
    • 节流与动画帧(requestAnimationFrame):结合raf实现流畅动画。
  • 缓存算法
    • LFU(最不经常使用):淘汰访问频率最低的数据。
    • ARC(自适应替换算法):结合LRU与LFU的混合策略。
  • 真实场景
    • 浏览器缓存机制:Cookie、localStorage与Service Worker的协作。
    • CDN缓存:通过HTTP头控制缓存策略(如Cache-Control)。

总结

本学案通过防抖、节流与缓存的实现原理、代码示例及生活案例,系统讲解了如何控制事件频率与优化性能。学生需理解:防抖与节流是前端优化的“时间管理术”,而缓存是“空间换时间”的经典策略。通过结合Map的高效存储能力,可实现灵活且高效的缓存管理,平衡性能与资源占用。

Built by Vitepress | Apache 2.0 Licensed