2026-01-19 10:21:18 +08:00

298 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 性能与优化
## 1. 图片懒加载
> **场景**:电商商品列表、新闻资讯页、图片画廊、任何大量图片展示页面。
> **解决**:首屏加载大量图片消耗带宽和性能,延迟加载可视区域外的图片。
### IntersectionObserver 方案(推荐)
```js
function lazyLoadImages() {
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) { // 图片进入可视区域
const img = entry.target;
img.src = img.dataset.src; // 加载真实图片
img.classList.remove('lazy');
observer.unobserve(img); // 加载完成后停止观察
}
});
}, {
rootMargin: '50px' // 提前 50px 开始加载,优化体验
});
// 观察所有带 lazy 类的图片
document.querySelectorAll('img.lazy').forEach(img => observer.observe(img));
}
// HTML: <img class="lazy" data-src="real-image.jpg" src="placeholder.jpg" />
```
### 传统滚动监听方案
```js
function lazyLoadScroll() {
const lazyImages = document.querySelectorAll('img.lazy');
// 节流处理,避免频繁触发
const loadImage = throttle(() => {
lazyImages.forEach(img => {
// 图片顶部进入视口下方 100px 范围内
if (img.getBoundingClientRect().top < window.innerHeight + 100) {
img.src = img.dataset.src;
img.classList.remove('lazy');
}
});
}, 200); // 每 200ms 最多执行一次
window.addEventListener('scroll', loadImage);
loadImage(); // 初始加载可见的图片
}
```
---
## 2. 虚拟列表(大数据渲染优化)
> **场景**:聊天记录、日志列表、表格万级数据、任何长列表滚动场景。
> **解决**:渲染万级 DOM 节点卡顿,只渲染可视区域的元素,大幅提升性能。
```js
class VirtualList {
constructor({ container, itemHeight, totalItems, renderItem }) {
this.container = container;
this.itemHeight = itemHeight;
this.totalItems = totalItems;
this.renderItem = renderItem;
this.visibleCount = Math.ceil(container.clientHeight / itemHeight) + 2;
this.startIndex = 0;
this.init();
}
init() {
// 创建容器结构
this.container.style.overflow = 'auto';
this.container.style.position = 'relative';
// 占位元素,撑开滚动高度
this.phantom = document.createElement('div');
this.phantom.style.height = `${this.totalItems * this.itemHeight}px`;
// 实际渲染的列表容器
this.content = document.createElement('div');
this.content.style.position = 'absolute';
this.content.style.top = '0';
this.content.style.width = '100%';
this.container.appendChild(this.phantom);
this.container.appendChild(this.content);
this.container.addEventListener('scroll', throttle(() => this.onScroll(), 16));
this.render();
}
onScroll() {
const scrollTop = this.container.scrollTop;
this.startIndex = Math.floor(scrollTop / this.itemHeight);
this.content.style.transform = `translateY(${this.startIndex * this.itemHeight}px)`;
this.render();
}
render() {
const fragment = document.createDocumentFragment();
const endIndex = Math.min(this.startIndex + this.visibleCount, this.totalItems);
for (let i = this.startIndex; i < endIndex; i++) {
const item = this.renderItem(i);
item.style.height = `${this.itemHeight}px`;
fragment.appendChild(item);
}
this.content.innerHTML = '';
this.content.appendChild(fragment);
}
}
// 使用示例
new VirtualList({
container: document.getElementById('list'),
itemHeight: 50,
totalItems: 10000,
renderItem: (index) => {
const div = document.createElement('div');
div.textContent = `Item ${index}`;
return div;
}
});
```
---
## 3. 函数节流在滚动事件中的应用
> **场景**:吸顶效果、返回顶部按钮显隐、数据埋点、滚动位置计算。
> **解决**:滚动事件每秒触发数十次,频繁执行回调导致页面卡顿。
```js
// 节流函数:确保最后一次触发也会执行
function throttle(fn, delay) {
let lastTime = 0; // 上次执行时间
let timer = null; // 定时器 ID
return function(...args) {
const now = Date.now();
const remaining = delay - (now - lastTime); // 剩余等待时间
if (remaining <= 0) {
// 已超过等待时间,立即执行
if (timer) {
clearTimeout(timer);
timer = null;
}
lastTime = now;
fn.apply(this, args);
} else if (!timer) {
// 未到时间且没有定时器,设置定时器保证最后一次执行
timer = setTimeout(() => {
lastTime = Date.now();
timer = null;
fn.apply(this, args);
}, remaining);
}
};
}
// 应用场景示例
window.addEventListener('scroll', throttle(() => {
console.log('滚动位置:', window.scrollY);
// 吸顶效果、返回顶部按钮显示等
}, 100)); // 每 100ms 最多执行一次
window.addEventListener('resize', throttle(() => {
console.log('窗口大小:', window.innerWidth, window.innerHeight);
}, 200));
```
---
## 4. requestAnimationFrame 优化动画
> **场景**平滑滚动、CSS 动画替代方案、Canvas/WebGL 渲染循环、游戏开发。
> **解决**setTimeout/setInterval 动画帧率不稳定rAF 与屏幕刷新率同步,动画更流畅。
```js
// 使用 requestAnimationFrame 实现平滑滚动
function smoothScrollTo(target, duration = 500) {
const start = window.scrollY; // 起始位置
const distance = target - start; // 滚动距离
const startTime = performance.now(); // 高精度起始时间
function step(currentTime) {
const elapsed = currentTime - startTime; // 已经过的时间
const progress = Math.min(elapsed / duration, 1); // 进度 0-1
// 缓动函数easeOutCubic动画先快后慢
const easeProgress = 1 - Math.pow(1 - progress, 3);
window.scrollTo(0, start + distance * easeProgress);
if (progress < 1) {
requestAnimationFrame(step); // 继续下一帧
}
}
requestAnimationFrame(step); // 开始动画
}
// rAF 节流:与屏幕刷新率同步
function rafThrottle(fn) {
let ticking = false; // 是否已请求下一帧
return function(...args) {
if (!ticking) {
requestAnimationFrame(() => {
fn.apply(this, args);
ticking = false;
});
ticking = true;
}
};
}
```
---
## 5. Web Worker 处理耗时任务
> **场景**大文件解析Excel/CSV、图像处理、复杂计算加密/压缩)、数据分析。
> **解决**耗时JS计算阻塞主线程导致页面卡顿Worker 在后台线程执行不影响UI。
```js
// 主线程 (main.js)
const worker = new Worker('worker.js'); // 创建后台线程
// 发送数据给 Worker
worker.postMessage({ type: 'heavyTask', data: largeArray });
// 接收 Worker 返回的结果
worker.onmessage = (e) => {
console.log('处理结果:', e.data);
};
// Worker 线程 (worker.js)
self.onmessage = (e) => {
if (e.data.type === 'heavyTask') {
// 在后台线程执行耗时计算,不影响主线程 UI
const result = e.data.data.map(item => /* 耗时计算 */);
self.postMessage(result); // 返回结果给主线程
}
};
```
---
## 6. 时间切片(避免长任务阻塞)
> **场景**React Fiber 架构原理、大量 DOM 初始化、批量数据处理不卡顿UI。
> **解决**长任务占据主线程超过50ms导致用户交互无响应分片执行保证响应性。
```js
// 时间切片:将长任务分成小块执行
async function timeSlice(tasks, chunkSize = 5) {
const results = [];
for (let i = 0; i < tasks.length; i += chunkSize) {
const chunk = tasks.slice(i, i + chunkSize);
// 处理一批任务
for (const task of chunk) {
results.push(task());
}
// 让出主线程,让浏览器有机会处理用户交互
await new Promise(resolve => setTimeout(resolve, 0));
}
return results;
}
// 使用 requestIdleCallback在浏览器空闲时执行
function processInIdle(tasks) {
let index = 0;
function work(deadline) {
// 当前帧还有剩余时间时执行任务
while (index < tasks.length && deadline.timeRemaining() > 0) {
tasks[index++]();
}
// 如果还有任务,等待下次空闲时继续
if (index < tasks.length) {
requestIdleCallback(work);
}
}
requestIdleCallback(work); // 开始执行
}
```