星光不问赶路人,时光不负有心人!
当你的网页变得卡顿、滚动不流畅、甚至频繁崩溃时,你是否想化身“代码医生”精准诊断问题?本节课将传授 防抖节流、虚拟滚动、内存管理 三大绝技,用「大型数据列表优化」实战让你的应用丝滑如德芙!
一、性能问题三大“元凶”
1. 过度重复操作
场景: 搜索框输入实时请求、窗口滚动频繁计算
解法: 防抖(debounce)与节流(throttle)
2. 庞大 DOM 操作
场景: 渲染千行表格、无限滚动加载
解法: 虚拟滚动(Virtual Scroll)
3. 内存泄漏
场景: 未清理的定时器、遗忘的事件监听、游离的 DOM 引用
解法: 及时回收资源
二、防抖与节流:给代码装上“刹车”
1. 防抖(Debounce):最后一人上电梯
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 使用:输入停止300ms后触发搜索
input.addEventListener('input', debounce(search, 300));
2. 节流(Throttle):发车时间表
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
fn.apply(this, args);
lastTime = now;
}
};
}
// 使用:滚动时每200ms计算一次
window.addEventListener('scroll', throttle(calculatePosition, 200));
防抖: 等你一段时间内没有再触发动作,才会执行一次。
节流: 每隔固定时间就执行一次,不管你在那段时间内有没有触发动作。
三、虚拟滚动:渲染海量数据的“障眼法”
1. 核心原理
- 可视区域渲染:只渲染用户能看到的 10~20 条数据
- 动态计算位置:通过滚动位置推算显示内容
2. 实战代码(简化版)
<!DOCTYPE html>
<html>
<head>
<style>
#container {
height: 600px;
overflow-y: auto;
border: 1px solid #ccc;
}
.item {
height: 30px;
line-height: 30px;
padding: 0 10px;
border-bottom: 1px solid #eee;
box-sizing: border-box;
}
</style>
</head>
<body>
<div id="container">
<div id="content"></div>
</div>
<script>
// 生成模拟数据
const data = Array.from({length: 100000}, (_, i) => `Item ${i + 1}`);
// 获取容器元素
const container = document.getElementById('container');
const content = document.getElementById('content');
// 配置参数
const itemHeight = 30; // 每个项目的高度
const buffer = 10; // 缓冲区项目数
let visibleCount = 0; // 可见区域项目数
let startIndex = 0; // 起始索引
let endIndex = 0; // 结束索引
// 初始化容器
function init() {
// 计算可见区域可容纳项目数
visibleCount = Math.ceil(container.clientHeight / itemHeight);
// 设置内容容器总高度(撑开滚动条)
content.style.height = `${data.length * itemHeight}px`;
// 初始渲染
updateList();
// 监听滚动事件
container.addEventListener('scroll', () => {
requestAnimationFrame(updateList);
});
}
// 更新列表
function updateList() {
// 计算新的索引(包含缓冲区)
const scrollTop = container.scrollTop;
const newStartIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - buffer);
const newEndIndex = Math.min(
data.length,
newStartIndex + visibleCount + buffer * 2
);
// 如果索引没有变化则不需要更新
if (startIndex === newStartIndex && endIndex === newEndIndex) return;
// 更新索引
startIndex = newStartIndex;
endIndex = newEndIndex;
// 创建文档片段
const fragment = document.createDocumentFragment();
// 添加当前需要显示的项目
for (let i = startIndex; i < endIndex; i++) {
const item = document.createElement('div');
item.className = 'item';
item.textContent = data[i];
fragment.appendChild(item);
}
// 更新DOM
content.innerHTML = '';
content.appendChild(fragment);
// 调整内容容器的padding(保持滚动位置)
content.style.paddingTop = `${startIndex * itemHeight}px`;
content.style.paddingBottom = `${(data.length - endIndex) * itemHeight}px`;
}
// 初始化
init();
</script>
</body>
</html>
效果对比:
-
传统渲染:100000 条 → 严重卡顿
-
虚拟滚动:仅渲染可视区域 → 流畅滚动
四、内存泄漏排查与预防
1. 常见泄露场景
-
未清除的 setInterval/setTimeout
-
未解绑的事件监听器
-
闭包中意外保留的变量引用
3. 预防技巧
// 定时器清理
const timer = setInterval(...);
// 组件销毁时
clearInterval(timer);
// 事件监听解绑
element.addEventListener('click', handler);
// 不再需要时
element.removeEventListener('click', handler);
// 清除DOM引用
const elements = document.querySelectorAll('.temp');
elements.forEach(el => el.remove());