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();
}
特性:
- 自动管理键值对:通过
Map
的has
、get
、set
方法操作缓存。 - 生命周期控制:可通过
clear
或LRU策略管理缓存大小。
知识回顾
- 防抖与节流:通过延迟或间隔执行控制事件频率,解决性能问题。
- 缓存:通过存储计算结果或数据副本提升效率,需权衡存储与一致性。
- 映射表(Map):提供高效的键值对存储,是实现LRU缓存的核心数据结构。
课后练习
选择题:以下哪种场景更适合使用防抖而非节流?
- A. 用户滚动页面时加载新内容
- B. 搜索框输入时实时搜索建议
- C. 按钮快速连续点击
代码题:实现一个支持
maxAge
(缓存有效期)的LRU缓存类。分析题:解释为什么浏览器需要定期清理缓存,如何平衡缓存效率与存储占用?
扩展阅读
- 防抖节流进阶:
- 移动端适配:触摸事件的防抖优化(如
touchmove
)。 - 节流与动画帧(requestAnimationFrame):结合
raf
实现流畅动画。
- 移动端适配:触摸事件的防抖优化(如
- 缓存算法:
- LFU(最不经常使用):淘汰访问频率最低的数据。
- ARC(自适应替换算法):结合LRU与LFU的混合策略。
- 真实场景:
- 浏览器缓存机制:Cookie、localStorage与Service Worker的协作。
- CDN缓存:通过HTTP头控制缓存策略(如
Cache-Control
)。
总结
本学案通过防抖、节流与缓存的实现原理、代码示例及生活案例,系统讲解了如何控制事件频率与优化性能。学生需理解:防抖与节流是前端优化的“时间管理术”,而缓存是“空间换时间”的经典策略。通过结合Map
的高效存储能力,可实现灵活且高效的缓存管理,平衡性能与资源占用。