JavaScript 性能优化实战技巧
性能优化是前端开发中的重要话题。本文将分享一些实用的 JavaScript 性能优化技巧,帮助你构建更快、更流畅的 Web 应用。
🎯 性能优化的重要性
为什么性能很重要?
- 📱 用户体验: 快速响应提升用户满意度
- 💰 商业价值: 性能直接影响转化率和收入
- 🔍 SEO 排名: 搜索引擎偏爱快速加载的网站
- 🌍 可访问性: 在低端设备和慢网络下也能正常使用
性能指标
- FCP (First Contentful Paint): 首次内容绘制
- LCP (Largest Contentful Paint): 最大内容绘制
- FID (First Input Delay): 首次输入延迟
- CLS (Cumulative Layout Shift): 累积布局偏移
🚀 代码层面优化
1. 避免不必要的重复计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // ❌ 不好的做法
function processItems(items) {
for (let i = 0; i < items.length; i++) {
// 每次循环都会计算 items.length
processItem(items[i]);
}
}
// ✅ 优化后
function processItems(items) {
const length = items.length; // 缓存长度
for (let i = 0; i < length; i++) {
processItem(items[i]);
}
}
// ✅ 更好的做法
function processItems(items) {
items.forEach(processItem); // 使用原生方法
}
|
2. 使用适当的数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // ❌ 使用数组进行频繁查找
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
// ... 更多用户
];
function findUser(id) {
return users.find(user => user.id === id); // O(n) 时间复杂度
}
// ✅ 使用 Map 进行快速查找
const usersMap = new Map([
[1, { id: 1, name: 'Alice' }],
[2, { id: 2, name: 'Bob' }],
// ... 更多用户
]);
function findUser(id) {
return usersMap.get(id); // O(1) 时间复杂度
}
|
3. 函数防抖和节流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| // 防抖:延迟执行,如果在延迟期间再次触发,则重新计时
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 节流:限制执行频率
function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 使用示例
const searchInput = document.getElementById('search');
const debouncedSearch = debounce(performSearch, 300);
const throttledScroll = throttle(handleScroll, 100);
searchInput.addEventListener('input', debouncedSearch);
window.addEventListener('scroll', throttledScroll);
|
🎨 DOM 操作优化
1. 批量 DOM 操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| // ❌ 多次 DOM 操作
function addItems(items) {
const container = document.getElementById('container');
items.forEach(item => {
const div = document.createElement('div');
div.textContent = item.text;
container.appendChild(div); // 每次都触发重排
});
}
// ✅ 使用 DocumentFragment
function addItems(items) {
const container = document.getElementById('container');
const fragment = document.createDocumentFragment();
items.forEach(item => {
const div = document.createElement('div');
div.textContent = item.text;
fragment.appendChild(div);
});
container.appendChild(fragment); // 只触发一次重排
}
// ✅ 使用 innerHTML (适用于简单内容)
function addItems(items) {
const container = document.getElementById('container');
const html = items.map(item => `<div>${item.text}</div>`).join('');
container.innerHTML = html;
}
|
2. 虚拟滚动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| class VirtualScroller {
constructor(container, items, itemHeight) {
this.container = container;
this.items = items;
this.itemHeight = itemHeight;
this.visibleCount = Math.ceil(container.clientHeight / itemHeight);
this.startIndex = 0;
this.render();
this.bindEvents();
}
render() {
const endIndex = Math.min(
this.startIndex + this.visibleCount,
this.items.length
);
const visibleItems = this.items.slice(this.startIndex, endIndex);
this.container.innerHTML = visibleItems
.map((item, index) => `
<div style="
position: absolute;
top: ${(this.startIndex + index) * this.itemHeight}px;
height: ${this.itemHeight}px;
">
${item.content}
</div>
`)
.join('');
// 设置容器总高度
this.container.style.height = `${this.items.length * this.itemHeight}px`;
}
bindEvents() {
this.container.addEventListener('scroll', () => {
const newStartIndex = Math.floor(this.container.scrollTop / this.itemHeight);
if (newStartIndex !== this.startIndex) {
this.startIndex = newStartIndex;
this.render();
}
});
}
}
|
⚡ 异步操作优化
1. 使用 Web Workers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| // main.js
function processLargeDataset(data) {
return new Promise((resolve, reject) => {
const worker = new Worker('worker.js');
worker.postMessage(data);
worker.onmessage = (e) => {
resolve(e.data);
worker.terminate();
};
worker.onerror = (error) => {
reject(error);
worker.terminate();
};
});
}
// worker.js
self.onmessage = function(e) {
const data = e.data;
// 执行耗时计算
const result = heavyComputation(data);
self.postMessage(result);
};
function heavyComputation(data) {
// 复杂的数据处理逻辑
return data.map(item => ({
...item,
processed: true,
timestamp: Date.now()
}));
}
|
2. 请求优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
| // 请求缓存
class RequestCache {
constructor() {
this.cache = new Map();
}
async get(url, options = {}) {
const cacheKey = `${url}${JSON.stringify(options)}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
const promise = fetch(url, options).then(res => res.json());
this.cache.set(cacheKey, promise);
try {
const result = await promise;
return result;
} catch (error) {
this.cache.delete(cacheKey); // 失败时清除缓存
throw error;
}
}
}
// 请求合并
class RequestBatcher {
constructor() {
this.pending = new Map();
}
async batchRequest(ids) {
const cacheKey = ids.sort().join(',');
if (this.pending.has(cacheKey)) {
return this.pending.get(cacheKey);
}
const promise = this.fetchMultiple(ids);
this.pending.set(cacheKey, promise);
try {
const result = await promise;
return result;
} finally {
this.pending.delete(cacheKey);
}
}
async fetchMultiple(ids) {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ids })
});
return response.json();
}
}
|
🧠 内存管理
1. 避免内存泄漏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| // ❌ 可能导致内存泄漏
class Component {
constructor() {
this.data = new Array(1000000).fill(0);
// 忘记清理事件监听器
window.addEventListener('resize', this.handleResize);
// 忘记清理定时器
this.timer = setInterval(this.update, 1000);
}
handleResize() {
// 处理窗口大小变化
}
update() {
// 更新组件
}
}
// ✅ 正确的内存管理
class Component {
constructor() {
this.data = new Array(1000000).fill(0);
// 绑定 this 并保存引用以便清理
this.handleResize = this.handleResize.bind(this);
window.addEventListener('resize', this.handleResize);
this.timer = setInterval(() => this.update(), 1000);
}
destroy() {
// 清理事件监听器
window.removeEventListener('resize', this.handleResize);
// 清理定时器
clearInterval(this.timer);
// 清理大对象
this.data = null;
}
handleResize() {
// 处理窗口大小变化
}
update() {
// 更新组件
}
}
|
2. 使用 WeakMap 和 WeakSet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| // 使用 WeakMap 存储私有数据
const privateData = new WeakMap();
class User {
constructor(name, email) {
privateData.set(this, { name, email });
}
getName() {
return privateData.get(this).name;
}
getEmail() {
return privateData.get(this).email;
}
}
// 使用 WeakSet 跟踪对象状态
const processedItems = new WeakSet();
function processItem(item) {
if (processedItems.has(item)) {
return; // 已处理过,跳过
}
// 处理逻辑
doSomethingWith(item);
processedItems.add(item);
}
|
📊 性能监控
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| // 测量函数执行时间
function measurePerformance(name, fn) {
return function (...args) {
const start = performance.now();
const result = fn.apply(this, args);
const end = performance.now();
console.log(`${name} 执行时间: ${end - start}ms`);
return result;
};
}
// 使用示例
const optimizedFunction = measurePerformance('数据处理', processData);
// 监控页面性能
function monitorPagePerformance() {
window.addEventListener('load', () => {
setTimeout(() => {
const navigation = performance.getEntriesByType('navigation')[0];
const paint = performance.getEntriesByType('paint');
console.log('页面加载时间:', navigation.loadEventEnd - navigation.fetchStart);
console.log('DOM 解析时间:', navigation.domContentLoadedEventEnd - navigation.domLoading);
paint.forEach(entry => {
console.log(`${entry.name}:`, entry.startTime);
});
}, 0);
});
}
|
2. 自定义性能监控
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| class PerformanceMonitor {
constructor() {
this.metrics = new Map();
}
startTimer(name) {
this.metrics.set(name, performance.now());
}
endTimer(name) {
const startTime = this.metrics.get(name);
if (startTime) {
const duration = performance.now() - startTime;
console.log(`${name}: ${duration.toFixed(2)}ms`);
this.metrics.delete(name);
return duration;
}
}
measureAsync(name, asyncFn) {
return async (...args) => {
this.startTimer(name);
try {
const result = await asyncFn(...args);
return result;
} finally {
this.endTimer(name);
}
};
}
}
// 使用示例
const monitor = new PerformanceMonitor();
const fetchData = monitor.measureAsync('API请求', async (url) => {
const response = await fetch(url);
return response.json();
});
|
🎯 实际应用案例
大列表渲染优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| class OptimizedList {
constructor(container, data) {
this.container = container;
this.data = data;
this.itemHeight = 50;
this.visibleCount = Math.ceil(container.clientHeight / this.itemHeight);
this.buffer = 5; // 缓冲区
this.init();
}
init() {
this.createScrollContainer();
this.bindEvents();
this.render();
}
createScrollContainer() {
this.scrollContainer = document.createElement('div');
this.scrollContainer.style.height = `${this.data.length * this.itemHeight}px`;
this.scrollContainer.style.position = 'relative';
this.container.appendChild(this.scrollContainer);
}
render() {
const scrollTop = this.container.scrollTop;
const startIndex = Math.max(0, Math.floor(scrollTop / this.itemHeight) - this.buffer);
const endIndex = Math.min(
this.data.length,
startIndex + this.visibleCount + this.buffer * 2
);
// 清空现有内容
this.scrollContainer.innerHTML = '';
// 渲染可见项目
for (let i = startIndex; i < endIndex; i++) {
const item = this.createItem(this.data[i], i);
this.scrollContainer.appendChild(item);
}
}
createItem(data, index) {
const item = document.createElement('div');
item.style.position = 'absolute';
item.style.top = `${index * this.itemHeight}px`;
item.style.height = `${this.itemHeight}px`;
item.style.width = '100%';
item.textContent = data.name;
return item;
}
bindEvents() {
let ticking = false;
this.container.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
this.render();
ticking = false;
});
ticking = true;
}
});
}
}
|
📝 性能优化清单
代码优化
- ✅ 避免不必要的重复计算
- ✅ 使用适当的数据结构
- ✅ 实现防抖和节流
- ✅ 优化循环和条件判断
DOM 优化
- ✅ 批量 DOM 操作
- ✅ 使用 DocumentFragment
- ✅ 实现虚拟滚动
- ✅ 避免强制同步布局
异步优化
- ✅ 使用 Web Workers 处理重计算
- ✅ 实现请求缓存和合并
- ✅ 优化 Promise 链
- ✅ 合理使用 async/await
内存管理
- ✅ 及时清理事件监听器
- ✅ 清理定时器和动画
- ✅ 使用 WeakMap/WeakSet
- ✅ 避免闭包陷阱
🚀 总结
JavaScript 性能优化是一个持续的过程,需要:
- 测量优先: 先测量再优化,避免过早优化
- 渐进改进: 逐步优化,不要一次性重写
- 监控反馈: 持续监控性能指标
- 用户体验: 始终以用户体验为中心
记住:过早的优化是万恶之源,但合理的性能优化能显著提升用户体验!
下一篇预告: 《Node.js 服务端性能优化实战》
有任何问题欢迎在评论区讨论!⚡