前端高性能优化技术详解
在现代前端开发中,性能优化已成为衡量一个应用质量的重要指标。随着应用复杂度的不断增加,如何确保页面流畅运行、避免卡顿、提高用户体验成为了前端工程师必须面对的重要课题。本文将深入探讨五项关键技术:虚拟滚动、时间切片(Time Slicing)、Web Workers多线程、内存泄漏检测和处理以及防抖节流的高级应用,帮助您掌握构建高性能前端应用的核心技能。
一、虚拟滚动(Virtual Scrolling)
1.1 虚拟滚动概述
虚拟滚动是一种优化长列表渲染性能的技术。传统的列表渲染会一次性渲染所有数据项,即使它们不在可视区域内,这会导致大量的DOM节点创建和内存占用。虚拟滚动只渲染当前可视区域内的数据项,以及少量缓冲区的数据项,从而大幅减少DOM节点数量,显著提升渲染性能。
1.2 虚拟滚动的实现原理
虚拟滚动的核心思想是动态计算可视区域内的元素,并只渲染这些元素。主要涉及以下几个关键概念:
- 可视区域高度:容器可见的高度
- 元素高度:每个列表项的高度
- 可视区域元素数量:可视区域内能够显示的元素数量
- 滚动偏移量:用户滚动的距离
- 起始索引和结束索引:当前需要渲染的元素范围
1.3 虚拟滚动的实现步骤
1.3.1 基础实现
让我们先来看一个最简单的虚拟滚动实现:
javascript
class VirtualList {
constructor(options) {
this.container = options.container;
this.itemHeight = options.itemHeight;
this.data = options.data;
this.visibleCount = Math.ceil(this.container.clientHeight / this.itemHeight);
this.bufferCount = options.bufferCount || 5;
this.startIndex = 0;
this.endIndex = this.visibleCount + this.bufferCount;
this.init();
}
init() {
this.render();
this.bindEvents();
}
bindEvents() {
this.container.addEventListener('scroll', () => {
this.updateVisibleItems();
});
}
updateVisibleItems() {
const scrollTop = this.container.scrollTop;
const newStartIndex = Math.max(0, Math.floor(scrollTop / this.itemHeight) - this.bufferCount);
const newEndIndex = Math.min(
this.data.length,
newStartIndex + this.visibleCount + this.bufferCount * 2
);
if (newStartIndex !== this.startIndex || newEndIndex !== this.endIndex) {
this.startIndex = newStartIndex;
this.endIndex = newEndIndex;
this.render();
}
}
render() {
const totalHeight = this.data.length * this.itemHeight;
const offsetY = this.startIndex * this.itemHeight;
const visibleData = this.data.slice(this.startIndex, this.endIndex);
// 设置容器总高度以维持滚动条正确显示
this.container.style.height = totalHeight + 'px';
// 创建内容容器
const contentWrapper = document.createElement('div');
contentWrapper.style.transform = `translateY(${offsetY}px)`;
// 渲染可见项
visibleData.forEach((item, index) => {
const element = document.createElement('div');
element.style.height = this.itemHeight + 'px';
element.textContent = item;
contentWrapper.appendChild(element);
});
// 清空并重新渲染
this.container.innerHTML = '';
this.container.appendChild(contentWrapper);
}
}
1.3.2 改进版虚拟滚动
上面的基础实现存在一些问题,比如每次滚动都会重新创建DOM元素。我们可以进一步优化:
javascript
class AdvancedVirtualList {
constructor(options) {
this.container = options.container;
this.itemHeight = options.itemHeight;
this.data = options.data;
this.visibleCount = Math.ceil(this.container.clientHeight / this.itemHeight);
this.bufferCount = options.bufferCount || 5;
this.startIndex = 0;
this.endIndex = this.visibleCount + this.bufferCount;
// 缓存DOM元素
this.itemElements = [];
this.contentWrapper = null;
this.init();
}
init() {
this.createStructure();
this.render();
this.bindEvents();
}
createStructure() {
// 创建总容器
this.totalContainer = document.createElement('div');
this.totalContainer.style.position = 'relative';
this.totalContainer.style.height = (this.data.length * this.itemHeight) + 'px';
// 创建可视区域容器
this.contentWrapper = document.createElement('div');
this.contentWrapper.style.position = 'absolute';
this.contentWrapper.style.top = '0';
this.contentWrapper.style.left = '0';
this.contentWrapper.style.right = '0';
this.container.appendChild(this.totalContainer);
this.totalContainer.appendChild(this.contentWrapper);
// 预创建元素池
const maxElements = this.visibleCount + this.bufferCount * 2;
for (let i = 0; i < maxElements; i++) {
const element = document.createElement('div');
element.style.height = this.itemHeight + 'px';
this.itemElements.push(element);
this.contentWrapper.appendChild(element);
}
}
bindEvents() {
this.container.addEventListener('scroll', this.throttle(() => {
this.updateVisibleItems();
}, 16));
}
throttle(func, delay) {
let timeoutId;
let lastExecTime = 0;
return function (...args) {
const currentTime = Date.now();
if (currentTime - lastExecTime > delay) {
func.apply(this, args);
lastExecTime = currentTime;
} else {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
lastExecTime = Date.now();
}, delay - (currentTime - lastExecTime));
}
};
}
updateVisibleItems() {
const scrollTop = this.container.scrollTop;
const newStartIndex = Math.max(0, Math.floor(scrollTop / this.itemHeight) - this.bufferCount);
const newEndIndex = Math.min(
this.data.length,
newStartIndex + this.visibleCount + this.bufferCount * 2
);
if (newStartIndex !== this.startIndex || newEndIndex !== this.endIndex) {
this.startIndex = newStartIndex;
this.endIndex = newEndIndex;
this.render();
}
}
render() {
const offsetY = this.startIndex * this.itemHeight;
this.contentWrapper.style.transform = `translateY(${offsetY}px)`;
const visibleData = this.data.slice(this.startIndex, this.endIndex);
// 更新可见元素
this.itemElements.forEach((element, index) => {
if (index < visibleData.length) {
element.textContent = visibleData[index];
element.style.display = 'block';
} else {
element.style.display = 'none';
}
});
}
}
1.4 虚拟滚动的高级特性
1.4.1 动态高度支持
现实场景中,列表项的高度往往是不固定的。我们需要支持动态高度的虚拟滚动:
javascript
class DynamicHeightVirtualList {
constructor(options) {
this.container = options.container;
this.data = options.data;
this.estimateHeight = options.estimateHeight || 50;
this.visibleCount = Math.ceil(this.container.clientHeight / this.estimateHeight);
this.bufferCount = options.bufferCount || 5;
// 缓存每个元素的实际高度
this.heights = new Array(this.data.length).fill(this.estimateHeight);
this.positions = this.calculatePositions();
this.startIndex = 0;
this.endIndex = this.visibleCount + this.bufferCount;
this.itemElements = [];
this.contentWrapper = null;
this.init();
}
calculatePositions() {
const positions = [];
let accumulatedHeight = 0;
for (let i = 0; i < this.data.length; i++) {
positions[i] = accumulatedHeight;
accumulatedHeight += this.heights[i];
}
return positions;
}
updateItemHeight(index, height) {
if (this.heights[index] !== height) {
const diff = height - this.heights[index];
this.heights[index] = height;
// 更新后续元素的位置
for (let i = index + 1; i < this.positions.length; i++) {
this.positions[i] += diff;
}
}
}
getVisibleRange(scrollTop, containerHeight) {
let start = 0;
let end = this.data.length;
// 找到起始索引
for (let i = 0; i < this.positions.length; i++) {
if (this.positions[i] >= scrollTop) {
start = Math.max(0, i - this.bufferCount);
break;
}
}
// 找到结束索引
const scrollBottom = scrollTop + containerHeight;
for (let i = start; i < this.positions.length; i++) {
if (this.positions[i] >= scrollBottom) {
end = Math.min(this.data.length, i + this.bufferCount);
break;
}
}
return { start, end };
}
// 其他方法类似前面的实现...
}
1.4.2 水平虚拟滚动
除了垂直滚动,我们还可以实现水平方向的虚拟滚动:
javascript
class HorizontalVirtualList {
constructor(options) {
this.container = options.container;
this.itemWidth = options.itemWidth;
this.data = options.data;
this.visibleCount = Math.ceil(this.container.clientWidth / this.itemWidth);
this.bufferCount = options.bufferCount || 5;
this.startIndex = 0;
this.endIndex = this.visibleCount + this.bufferCount;
this.init();
}
updateVisibleItems() {
const scrollLeft = this.container.scrollLeft;
const newStartIndex = Math.max(0, Math.floor(scrollLeft / this.itemWidth) - this.bufferCount);
const newEndIndex = Math.min(
this.data.length,
newStartIndex + this.visibleCount + this.bufferCount * 2
);
if (newStartIndex !== this.startIndex || newEndIndex !== this.endIndex) {
this.startIndex = newStartIndex;
this.endIndex = newEndIndex;
this.render();
}
}
render() {
const offsetX = this.startIndex * this.itemWidth;
const visibleData = this.data.slice(this.startIndex, this.endIndex);
// 设置容器总宽度
this.container.style.width = (this.data.length * this.itemWidth) + 'px';
const contentWrapper = document.createElement('div');
contentWrapper.style.transform = `translateX(${offsetX}px)`;
contentWrapper.style.display = 'flex';
visibleData.forEach((item, index) => {
const element = document.createElement('div');
element.style.width = this.itemWidth + 'px';
element.style.flexShrink = '0';
element.textContent = item;
contentWrapper.appendChild(element);
});
this.container.innerHTML = '';
this.container.appendChild(contentWrapper);
}
}
1.5 虚拟滚动在现代框架中的应用
1.5.1 React中的虚拟滚动实现
jsx
import React, { useState, useEffect, useRef } from 'react';
const VirtualList = ({ items, itemHeight, renderItem }) => {
const containerRef = useRef(null);
const [visibleRange, setVisibleRange] = useState({ start: 0, end: 0 });
const [scrollTop, setScrollTop] = useState(0);
const visibleCount = Math.ceil(containerRef.current?.clientHeight / itemHeight || 0);
const bufferCount = 5;
useEffect(() => {
const handleScroll = () => {
if (containerRef.current) {
setScrollTop(containerRef.current.scrollTop);
}
};
const container = containerRef.current;
if (container) {
container.addEventListener('scroll', handleScroll);
return () => container.removeEventListener('scroll', handleScroll);
}
}, []);
useEffect(() => {
const newStart = Math.max(0, Math.floor(scrollTop / itemHeight) - bufferCount);
const newEnd = Math.min(items.length, newStart + visibleCount + bufferCount * 2);
setVisibleRange({ start: newStart, end: newEnd });
}, [scrollTop, items.length, itemHeight, visibleCount]);
const totalHeight = items.length * itemHeight;
const offsetY = visibleRange.start * itemHeight;
const visibleItems = items.slice(visibleRange.start, visibleRange.end);
return (
<div
ref={containerRef}
style={{
height: '100%',
overflow: 'auto',
position: 'relative'
}}
>
<div style={{ height: totalHeight }}>
<div
style={{
transform: `translateY(${offsetY}px)`,
position: 'absolute',
top: 0,
left: 0,
right: 0
}}
>
{visibleItems.map((item, index) => (
<div
key={visibleRange.start + index}
style={{ height: itemHeight }}
>
{renderItem(item)}
</div>
))}
</div>
</div>
</div>
);
};
1.5.2 Vue中的虚拟滚动实现
vue
<template>
<div
ref="container"
class="virtual-list"
@scroll="handleScroll"
>
<div :style="{ height: totalHeight + 'px' }">
<div
class="virtual-list-content"
:style="{ transform: `translateY(${offsetY}px)` }"
>
<div
v-for="(item, index) in visibleItems"
:key="visibleStartIndex + index"
:style="{ height: itemHeight + 'px' }"
class="virtual-list-item"
>
<slot :item="item" :index="visibleStartIndex + index" />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VirtualList',
props: {
items: {
type: Array,
required: true
},
itemHeight: {
type: Number,
default: 50
},
bufferSize: {
type: Number,
default: 5
}
},
data() {
return {
scrollTop: 0
};
},
computed: {
containerHeight() {
return this.$refs.container ? this.$refs.container.clientHeight : 0;
},
visibleCount() {
return Math.ceil(this.containerHeight / this.itemHeight);
},
totalHeight() {
return this.items.length * this.itemHeight;
},
visibleStartIndex() {
return Math.max(0, Math.floor(this.scrollTop / this.itemHeight) - this.bufferSize);
},
visibleEndIndex() {
return Math.min(
this.items.length,
this.visibleStartIndex + this.visibleCount + this.bufferSize * 2
);
},
visibleItems() {
return this.items.slice(this.visibleStartIndex, this.visibleEndIndex);
},
offsetY() {
return this.visibleStartIndex * this.itemHeight;
}
},
methods: {
handleScroll(event) {
this.scrollTop = event.target.scrollTop;
}
}
};
</script>
<style scoped>
.virtual-list {
height: 100%;
overflow: auto;
position: relative;
}
.virtual-list-content {
position: absolute;
top: 0;
left: 0;
right: 0;
}
</style>
1.6 虚拟滚动的性能优化技巧
1.6.1 使用requestAnimationFrame优化渲染
javascript
class OptimizedVirtualList {
constructor(options) {
// ... 初始化代码
this.rafId = null;
this.pendingUpdate = false;
}
updateVisibleItems() {
if (!this.pendingUpdate) {
this.pendingUpdate = true;
this.rafId = requestAnimationFrame(() => {
this.performUpdate();
this.pendingUpdate = false;
});
}
}
performUpdate() {
const scrollTop = this.container.scrollTop;
const newStartIndex = Math.max(0, Math.floor(scrollTop / this.itemHeight) - this.bufferCount);
const newEndIndex = Math.min(
this.data.length,
newStartIndex + this.visibleCount + this.bufferCount * 2
);
if (newStartIndex !== this.startIndex || newEndIndex !== this.endIndex) {
this.startIndex = newStartIndex;
this.endIndex = newEndIndex;
this.render();
}
}
destroy() {
if (this.rafId) {
cancelAnimationFrame(this.rafId);
}
}
}
1.6.2 使用Intersection Observer优化可见性检测
javascript
class IntersectionObserverVirtualList {
constructor(options) {
// ... 初始化代码
this.intersectionObserver = new IntersectionObserver(
this.handleIntersection.bind(this),
{ root: this.container, threshold: 0 }
);
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 元素进入视口,可以进行懒加载等操作
const index = parseInt(entry.target.dataset.index);
this.loadItemData(index);
}
});
}
loadItemData(index) {
// 加载具体项的数据
}
}
二、时间切片(Time Slicing)
2.1 时间切片概述
时间切片是一种将长时间运行的任务分解为多个小任务片段执行的技术,每个片段执行时间很短,不会阻塞浏览器主线程,从而保证页面的响应性和流畅性。这是React Fiber架构的核心思想之一。
2.2 时间切片的实现原理
时间切片的核心是利用requestIdleCallback API或者setTimeout来调度任务,确保在浏览器空闲时间执行任务片段。
2.3 基础时间切片实现
javascript
class TimeSlicingScheduler {
constructor() {
this.tasks = [];
this.running = false;
}
addTask(task) {
this.tasks.push(task);
if (!this.running) {
this.run();
}
}
run() {
this.running = true;
const executeChunk = () => {
const startTime = performance.now();
// 在5毫秒内执行任务
while (this.tasks.length > 0 && performance.now() - startTime < 5) {
const task = this.tasks.shift();
task();
}
if (this.tasks.length > 0) {
// 如果还有任务,继续调度
if (typeof requestIdleCallback !== 'undefined') {
requestIdleCallback(executeChunk);
} else {
setTimeout(executeChunk, 0);
}
} else {
this.running = false;
}
};
if (typeof requestIdleCallback !== 'undefined') {
requestIdleCallback(executeChunk);
} else {
setTimeout(executeChunk, 0);
}
}
}
// 使用示例
const scheduler = new TimeSlicingScheduler();
// 添加大量任务
for (let i = 0; i < 10000; i++) {
scheduler.addTask(() => {
// 执行一些计算密集型任务
console.log('Processing item:', i);
});
}
2.4 高级时间切片实现
javascript
class AdvancedTimeSlicing {
constructor(options = {}) {
this.frameBudget = options.frameBudget || 5; // 每帧预算时间(毫秒)
this.tasks = [];
this.running = false;
this.paused = false;
this.onComplete = options.onComplete || (() => {});
this.onError = options.onError || ((error) => { throw error; });
}
addTask(task, priority = 0) {
// 按优先级插入任务
const insertIndex = this.tasks.findIndex(t => t.priority > priority);
const taskObj = { task, priority };
if (insertIndex === -1) {
this.tasks.push(taskObj);
} else {
this.tasks.splice(insertIndex, 0, taskObj);
}
if (!this.running && !this.paused) {
this.run();
}
}
pause() {
this.paused = true;
}
resume() {
this.paused = false;
if (this.tasks.length > 0 && !this.running) {
this.run();
}
}
clear() {
this.tasks = [];
this.running = false;
}
run() {
if (this.paused) return;
this.running = true;
const executeChunk = (deadline) => {
if (this.paused) {
this.running = false;
return;
}
try {
// 使用requestIdleCallback提供的deadline
let hasTimeRemaining = () => deadline.timeRemaining() > 0;
if (typeof deadline === 'undefined') {
// fallback到performance.now()
const startTime = performance.now();
hasTimeRemaining = () => performance.now() - startTime < this.frameBudget;
}
// 执行任务直到时间用完
while (this.tasks.length > 0 && hasTimeRemaining()) {
const taskObj = this.tasks.shift();
taskObj.task();
}
if (this.tasks.length > 0) {
// 还有任务,继续调度
this.scheduleNextChunk(executeChunk);
} else {
// 所有任务完成
this.running = false;
this.onComplete();
}
} catch (error) {
this.running = false;
this.onError(error);
}
};
this.scheduleNextChunk(executeChunk);
}
scheduleNextChunk(callback) {
if (typeof requestIdleCallback !== 'undefined') {
requestIdleCallback(callback);
} else {
// fallback到requestAnimationFrame
requestAnimationFrame(() => {
const deadline = {
timeRemaining: () => Math.max(0, this.frameBudget - (performance.now() % this.frameBudget))
};
callback(deadline);
});
}
}
}
// 使用示例
const scheduler = new AdvancedTimeSlicing({
frameBudget: 8,
onComplete: () => console.log('All tasks completed'),
onError: (error) => console.error('Task error:', error)
});
// 添加高优先级任务
scheduler.addTask(() => {
console.log('High priority task');
}, 10);
// 添加普通任务
for (let i = 0; i < 1000; i++) {
scheduler.addTask(() => {
// 模拟耗时操作
let sum = 0;
for (let j = 0; j < 10000; j++) {
sum += j;
}
console.log(`Task ${i} completed, sum: ${sum}`);
});
}
2.5 React中的时间切片应用
jsx
import React, { useState, useEffect, useCallback } from 'react';
// 时间切片Hook
function useTimeSlicing(initialTasks = [], options = {}) {
const [tasks, setTasks] = useState(initialTasks);
const [processing, setProcessing] = useState(false);
const [completed, setCompleted] = useState(0);
const processTasks = useCallback(() => {
if (tasks.length === 0 || processing) return;
setProcessing(true);
let processedCount = 0;
const executeChunk = (deadline) => {
try {
while (tasks.length > 0 && deadline.timeRemaining() > 0) {
const task = tasks.shift();
task();
processedCount++;
}
setCompleted(prev => prev + processedCount);
if (tasks.length > 0) {
requestIdleCallback(executeChunk);
} else {
setProcessing(false);
}
} catch (error) {
console.error('Error processing tasks:', error);
setProcessing(false);
}
};
requestIdleCallback(executeChunk);
}, [tasks, processing]);
useEffect(() => {
if (tasks.length > 0 && !processing) {
processTasks();
}
}, [tasks, processing, processTasks]);
const addTasks = useCallback((newTasks) => {
setTasks(prev => [...prev, ...newTasks]);
}, []);
return {
tasks,
processing,
completed,
addTasks,
setTasks
};
}
// 使用时间切片的组件
const TimeSlicingComponent = () => {
const [data, setData] = useState([]);
const { processing, completed, addTasks } = useTimeSlicing();
const generateTasks = () => {
const tasks = [];
for (let i = 0; i < 10000; i++) {
tasks.push(() => {
// 模拟复杂计算
const result = Math.pow(Math.random(), Math.random());
if (i % 1000 === 0) {
console.log(`Processed ${i} items`);
}
});
}
addTasks(tasks);
};
return (
<div>
<button onClick={generateTasks} disabled={processing}>
{processing ? 'Processing...' : 'Start Processing'}
</button>
<div>Completed: {completed}</div>
<div>Processing: {processing ? 'Yes' : 'No'}</div>
</div>
);
};
2.6 时间切片在数据处理中的应用
javascript
class DataProcessor {
constructor(options = {}) {
this.chunkSize = options.chunkSize || 100;
this.onProgress = options.onProgress || (() => {});
this.onComplete = options.onComplete || (() => {});
}
async processData(data) {
const results = [];
const total = data.length;
let processed = 0;
const processChunk = (chunk) => {
return new Promise(resolve => {
const executeChunk = (deadline) => {
const chunkResults = [];
while (chunk.length > 0 && deadline.timeRemaining() > 0) {
const item = chunk.shift();
// 处理单个数据项
const result = this.processItem(item);
chunkResults.push(result);
processed++;
}
results.push(...chunkResults);
this.onProgress(processed, total);
if (chunk.length > 0) {
requestIdleCallback(executeChunk);
} else {
resolve();
}
};
requestIdleCallback(executeChunk);
});
};
// 将数据分块处理
for (let i = 0; i < data.length; i += this.chunkSize) {
const chunk = data.slice(i, i + this.chunkSize);
await processChunk(chunk);
}
this.onComplete(results);
return results;
}
processItem(item) {
// 实际的数据处理逻辑
// 这里只是一个示例
return {
...item,
processed: true,
timestamp: Date.now()
};
}
}
// 使用示例
const processor = new DataProcessor({
chunkSize: 50,
onProgress: (processed, total) => {
console.log(`Progress: ${processed}/${total} (${Math.round(processed/total*100)}%)`);
},
onComplete: (results) => {
console.log('Processing completed:', results.length, 'items');
}
});
// 处理大量数据
const largeDataset = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
value: Math.random()
}));
processor.processData(largeDataset);
三、Web Workers多线程
3.1 Web Workers概述
Web Workers允许在后台线程中运行JavaScript代码,避免阻塞主线程,从而提高应用的响应性。这对于执行计算密集型任务特别有用。
3.2 Web Workers基础使用
3.2.1 主线程代码
javascript
// main.js
class WorkerManager {
constructor(workerScript) {
this.worker = new Worker(workerScript);
this.callbacks = new Map();
this.messageId = 0;
this.worker.onmessage = (event) => {
const { id, result, error } = event.data;
const callback = this.callbacks.get(id);
if (callback) {
if (error) {
callback.reject(new Error(error));
} else {
callback.resolve(result);
}
this.callbacks.delete(id);
}
};
this.worker.onerror = (error) => {
console.error('Worker error:', error);
};
}
postMessage(message) {
return new Promise((resolve, reject) => {
const id = ++this.messageId;
this.callbacks.set(id, { resolve, reject });
this.worker.postMessage({ id, ...message });
});
}
terminate() {
this.worker.terminate();
this.callbacks.clear();
}
}
// 使用Worker
const workerManager = new WorkerManager('worker.js');
// 发送任务到Worker
workerManager.postMessage({
command: 'calculate',
data: { numbers: [1, 2, 3, 4, 5] }
}).then(result => {
console.log('Calculation result:', result);
}).catch(error => {
console.error('Calculation failed:', error);
});
3.2.2 Worker线程代码
javascript
// worker.js
self.onmessage = function(event) {
const { id, command, data } = event.data;
try {
let result;
switch (command) {
case 'calculate':
result = performCalculation(data);
break;
case 'sort':
result = performSort(data);
break;
case 'filter':
result = performFilter(data);
break;
default:
throw new Error(`Unknown command: ${command}`);
}
self.postMessage({ id, result });
} catch (error) {
self.postMessage({ id, error: error.message });
}
};
function performCalculation(data) {
const { numbers } = data;
return numbers.reduce((sum, num) => sum + num, 0);
}
function performSort(data) {
const { array } = data;
return array.sort((a, b) => a - b);
}
function performFilter(data) {
const { array, predicate } = data;
return array.filter(item => eval(predicate)); // 注意:实际使用中应避免eval
}
3.3 高级Web Workers实现
3.3.1 Worker池管理
javascript
class WorkerPool {
constructor(workerScript, poolSize = 4) {
this.workerScript = workerScript;
this.poolSize = poolSize;
this.workers = [];
this.taskQueue = [];
this.workerQueue = [];
this.initializeWorkers();
}
initializeWorkers() {
for (let i = 0; i < this.poolSize; i++) {
const worker = new Worker(this.workerScript);
worker.id = i;
worker.busy = false;
worker.onmessage = (event) => {
const { id, result, error } = event.data;
const task = this.findTaskById(id);
if (task) {
if (error) {
task.reject(new Error(error));
} else {
task.resolve(result);
}
this.removeTask(id);
this.releaseWorker(worker);
}
};
worker.onerror = (error) => {
console.error(`Worker ${worker.id} error:`, error);
this.releaseWorker(worker);
};
this.workers.push(worker);
this.workerQueue.push(worker);
}
}
execute(taskData) {
return new Promise((resolve, reject) => {
const task = {
id: this.generateId(),
data: taskData,
resolve,
reject
};
this.taskQueue.push(task);
this.processQueue();
});
}
processQueue() {
while (this.taskQueue.length > 0 && this.workerQueue.length > 0) {
const task = this.taskQueue.shift();
const worker = this.workerQueue.shift();
worker.busy = true;
worker.postMessage({ id: task.id, ...task.data });
}
}
releaseWorker(worker) {
worker.busy = false;
this.workerQueue.push(worker);
this.processQueue();
}
findTaskById(id) {
return this.taskQueue.find(task => task.id === id);
}
removeTask(id) {
this.taskQueue = this.taskQueue.filter(task => task.id !== id);
}
generateId() {
return Date.now() + Math.random();
}
terminate() {
this.workers.forEach(worker => worker.terminate());
this.workers = [];
this.taskQueue = [];
this.workerQueue = [];
}
}
// 使用Worker池
const workerPool = new WorkerPool('advanced-worker.js', 4);
// 并发执行多个任务
Promise.all([
workerPool.execute({ command: 'fibonacci', n: 40 }),
workerPool.execute({ command: 'factorial', n: 20 }),
workerPool.execute({ command: 'prime', limit: 10000 })
]).then(results => {
console.log('All calculations completed:', results);
});
3.3.2 高级Worker实现
javascript
// advanced-worker.js
const commands = {
fibonacci: function(n) {
if (n <= 1) return n;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) {
const temp = a + b;
a = b;
b = temp;
}
return b;
},
factorial: function(n) {
if (n <= 1) return 1;
let result = 1;
for (let i = 2; i <= n; i++) {
result *= i;
}
return result;
},
prime: function(limit) {
const primes = [];
for (let i = 2; i <= limit; i++) {
let isPrime = true;
for (let j = 2; j <= Math.sqrt(i); j++) {
if (i % j === 0) {
isPrime = false;
break;
}
}
if (isPrime) primes.push(i);
}
return primes;
},
sort: function(data) {
const { array, algorithm = 'quick' } = data;
switch (algorithm) {
case 'bubble':
return bubbleSort(array);
case 'quick':
return quickSort(array);
case 'merge':
return mergeSort(array);
default:
return array.sort((a, b) => a - b);
}
},
search: function(data) {
const { array, target, algorithm = 'binary' } = data;
switch (algorithm) {
case 'linear':
return linearSearch(array, target);
case 'binary':
return binarySearch(array, target);
default:
return array.indexOf(target);
}
}
};
function bubbleSort(arr) {
const array = [...arr];
const n = array.length;
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (array[j] > array[j + 1]) {
[array[j], array[j + 1]] = [array[j + 1], array[j]];
}
}
}
return array;
}
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)];
const left = [];
const right = [];
const equal = [];
for (let element of arr) {
if (element < pivot) {
left.push(element);
} else if (element > pivot) {
right.push(element);
} else {
equal.push(element);
}
}
return [...quickSort(left), ...equal, ...quickSort(right)];
}
function mergeSort(arr) {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
function merge(left, right) {
const result = [];
let leftIndex = 0;
let rightIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] < right[rightIndex]) {
result.push(left[leftIndex]);
leftIndex++;
} else {
result.push(right[rightIndex]);
rightIndex++;
}
}
return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));
}
function linearSearch(arr, target) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) return i;
}
return -1;
}
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
self.onmessage = function(event) {
const { id, command, data } = event.data;
try {
if (!commands[command]) {
throw new Error(`Unknown command: ${command}`);
}
const result = commands[command](data);
self.postMessage({ id, result });
} catch (error) {
self.postMessage({ id, error: error.message });
}
};
3.4 Web Workers在图像处理中的应用
javascript
// image-worker.js
self.onmessage = function(event) {
const { id, command, imageData, options } = event.data;
try {
let result;
switch (command) {
case 'grayscale':
result = convertToGrayscale(imageData);
break;
case 'blur':
result = applyBlur(imageData, options.radius);
break;
case 'sharpen':
result = applySharpen(imageData);
break;
case 'edgeDetection':
result = detectEdges(imageData);
break;
default:
throw new Error(`Unknown command: ${command}`);
}
self.postMessage({ id, result }, [result.data.buffer]);
} catch (error) {
self.postMessage({ id, error: error.message });
}
};
function convertToGrayscale(imageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg; // red
data[i + 1] = avg; // green
data[i + 2] = avg; // blue
}
return imageData;
}
function applyBlur(imageData, radius) {
const { width, height, data } = imageData;
const newData = new Uint8ClampedArray(data.length);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = (y * width + x) * 4;
let r = 0, g = 0, b = 0, count = 0;
for (let dy = -radius; dy <= radius; dy++) {
for (let dx = -radius; dx <= radius; dx++) {
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
const nIdx = (ny * width + nx) * 4;
r += data[nIdx];
g += data[nIdx + 1];
b += data[nIdx + 2];
count++;
}
}
}
newData[idx] = r / count;
newData[idx + 1] = g / count;
newData[idx + 2] = b / count;
newData[idx + 3] = data[idx + 3]; // alpha
}
}
return new ImageData(newData, width, height);
}
// 主线程中使用
class ImageProcessor {
constructor() {
this.worker = new Worker('image-worker.js');
this.callbacks = new Map();
this.messageId = 0;
this.worker.onmessage = (event) => {
const { id, result, error } = event.data;
const callback = this.callbacks.get(id);
if (callback) {
if (error) {
callback.reject(new Error(error));
} else {
callback.resolve(result);
}
this.callbacks.delete(id);
}
};
}
processImage(imageData, command, options = {}) {
return new Promise((resolve, reject) => {
const id = ++this.messageId;
this.callbacks.set(id, { resolve, reject });
this.worker.postMessage({
id,
command,
imageData,
options
}, [imageData.data.buffer]);
});
}
terminate() {
this.worker.terminate();
this.callbacks.clear();
}
}
四、内存泄漏检测和处理
4.1 内存泄漏概述
内存泄漏是指程序在申请内存后,无法释放已申请的内存空间,导致可用内存逐渐减少,最终可能导致程序崩溃或系统性能下降。
4.2 常见内存泄漏场景
4.2.1 事件监听器未移除
javascript
class MemoryLeakExample {
constructor() {
this.elements = [];
this.handleClick = this.handleClick.bind(this);
this.init();
}
init() {
// 错误的做法:添加事件监听器但未移除
for (let i = 0; i < 1000; i++) {
const element = document.createElement('div');
element.addEventListener('click', this.handleClick);
document.body.appendChild(element);
this.elements.push(element);
}
}
handleClick(event) {
console.log('Element clicked');
}
// 正确的做法:提供清理方法
cleanup() {
this.elements.forEach(element => {
element.removeEventListener('click', this.handleClick);
document.body.removeChild(element);
});
this.elements = [];
}
destroy() {
this.cleanup();
}
}
4.2.2 定时器未清除
javascript
class TimerLeakExample {
constructor() {
this.intervals = [];
this.timeouts = [];
this.startTimers();
}
startTimers() {
// 错误的做法:启动定时器但未清除
const interval = setInterval(() => {
console.log('Interval tick');
}, 1000);
const timeout = setTimeout(() => {
console.log('Timeout executed');
}, 5000);
this.intervals.push(interval);
this.timeouts.push(timeout);
}
// 正确的做法:提供清理方法
clearTimers() {
this.intervals.forEach(interval => clearInterval(interval));
this.timeouts.forEach(timeout => clearTimeout(timeout));
this.intervals = [];
this.timeouts = [];
}
destroy() {
this.clearTimers();
}
}
4.2.3 闭包引用
javascript
class ClosureLeakExample {
constructor() {
this.data = new Array(1000000).fill('some data');
this.callbacks = [];
this.setupCallbacks();
}
setupCallbacks() {
// 错误的做法:闭包持有大量数据引用
for (let i = 0; i < 100; i++) {
const callback = () => {
// 这个闭包持有了this.data的引用
console.log(this.data.length);
};
this.callbacks.push(callback);
}
}
// 正确的做法:避免不必要的引用
setupSafeCallbacks() {
const dataLength = this.data.length; // 只保存需要的数据
for (let i = 0; i < 100; i++) {
const callback = () => {
console.log(dataLength);
};
this.callbacks.push(callback);
}
}
destroy() {
this.callbacks = null;
this.data = null;
}
}
4.3 内存泄漏检测工具
4.3.1 自定义内存监控工具
javascript
class MemoryMonitor {
constructor(options = {}) {
this.checkInterval = options.checkInterval || 5000;
this.threshold = options.threshold || 100; // MB
this.onLeakDetected = options.onLeakDetected || (() => {});
this.monitoring = false;
this.baseline = null;
this.history = [];
}
start() {
if (this.monitoring) return;
this.monitoring = true;
this.baseline = this.getCurrentMemoryUsage();
this.checkMemory();
}
stop() {
this.monitoring = false;
}
getCurrentMemoryUsage() {
if (performance.memory) {
return {
used: performance.memory.usedJSHeapSize / 1024 / 1024, // MB
total: performance.memory.totalJSHeapSize / 1024 / 1024, // MB
limit: performance.memory.jsHeapSizeLimit / 1024 / 1024 // MB
};
}
return null;
}
checkMemory() {
if (!this.monitoring) return;
const current = this.getCurrentMemoryUsage();
if (current) {
this.history.push({
timestamp: Date.now(),
memory: current
});
// 保留最近60个数据点
if (this.history.length > 60) {
this.history.shift();
}
// 检查是否有内存泄漏
if (this.baseline && current.used > this.baseline.used + this.threshold) {
this.onLeakDetected({
baseline: this.baseline,
current: current,
increase: current.used - this.baseline.used
});
}
}
setTimeout(() => this.checkMemory(), this.checkInterval);
}
getMemoryTrend() {
if (this.history.length < 2) return 0;
const first = this.history[0].memory.used;
const last = this.history[this.history.length - 1].memory.used;
return (last - first) / this.history.length;
}
}
// 使用示例
const monitor = new MemoryMonitor({
checkInterval: 3000,
threshold: 50,
onLeakDetected: (info) => {
console.warn('Potential memory leak detected:', info);
// 可以触发垃圾回收(仅在开发环境下有效)
if (window.gc) {
window.gc();
}
}
});
monitor.start();
4.3.2 DOM节点泄漏检测
javascript
class DomLeakDetector {
constructor() {
this.observers = [];
this.detachedNodes = new WeakMap();
this.leakThreshold = 1000; // 毫秒
}
watchElementRemoval(element, callback) {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.removedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
this.trackDetachedNode(node, callback);
}
});
});
});
observer.observe(element, { childList: true, subtree: true });
this.observers.push(observer);
}
trackDetachedNode(node, callback) {
// 标记节点为已分离
this.detachedNodes.set(node, {
timestamp: Date.now(),
callback: callback
});
// 延迟检查是否已被清理
setTimeout(() => {
if (this.detachedNodes.has(node)) {
const info = this.detachedNodes.get(node);
if (Date.now() - info.timestamp > this.leakThreshold) {
console.warn('Potential DOM node leak detected:', node);
if (info.callback) {
info.callback(node);
}
}
}
}, this.leakThreshold + 100);
}
cleanup() {
this.observers.forEach(observer => observer.disconnect());
this.observers = [];
this.detachedNodes = new WeakMap();
}
}
4.4 内存泄漏预防最佳实践
4.4.1 生命周期管理
javascript
class Component {
constructor() {
this.eventListeners = [];
this.timers = [];
this.subscriptions = [];
}
addEventListener(element, event, handler, options) {
element.addEventListener(event, handler, options);
this.eventListeners.push({ element, event, handler, options });
}
setInterval(callback, delay) {
const id = setInterval(callback, delay);
this.timers.push({ type: 'interval', id });
return id;
}
setTimeout(callback, delay) {
const id = setTimeout(callback, delay);
this.timers.push({ type: 'timeout', id });
return id;
}
subscribe(observable, callback) {
const subscription = observable.subscribe(callback);
this.subscriptions.push(subscription);
return subscription;
}
// 统一的销毁方法
destroy() {
// 清理事件监听器
this.eventListeners.forEach(({ element, event, handler, options }) => {
element.removeEventListener(event, handler, options);
});
this.eventListeners = [];
// 清理定时器
this.timers.forEach(({ type, id }) => {
if (type === 'interval') {
clearInterval(id);
} else {
clearTimeout(id);
}
});
this.timers = [];
// 清理订阅
this.subscriptions.forEach(subscription => {
if (subscription.unsubscribe) {
subscription.unsubscribe();
}
});
this.subscriptions = [];
// 清理其他资源
this.cleanup();
}
cleanup() {
// 子类可以重写此方法来清理特定资源
}
}
4.4.2 WeakMap和WeakSet的应用
javascript
class SafeEventManager {
constructor() {
// 使用WeakMap存储元素相关的数据,避免内存泄漏
this.elementData = new WeakMap();
this.eventHandlers = new WeakMap();
}
setData(element, key, value) {
if (!this.elementData.has(element)) {
this.elementData.set(element, new Map());
}
this.elementData.get(element).set(key, value);
}
getData(element, key) {
if (!this.elementData.has(element)) {
return undefined;
}
return this.elementData.get(element).get(key);
}
addEventHandler(element, event, handler) {
element.addEventListener(event, handler);
if (!this.eventHandlers.has(element)) {
this.eventHandlers.set(element, []);
}
this.eventHandlers.get(element).push({
event,
handler
});
}
removeAllEventHandlers(element) {
if (this.eventHandlers.has(element)) {
const handlers = this.eventHandlers.get(element);
handlers.forEach(({ event, handler }) => {
element.removeEventListener(event, handler);
});
// 当元素被垃圾回收时,WeakMap会自动清理相关数据
}
}
}
五、防抖节流高级应用
5.1 防抖(Debounce)详解
防抖是指触发事件后在n秒内只能执行一次,如果在n秒内又触发了事件,则会重新计算时间。
5.1.1 基础防抖实现
javascript
function debounce(func, delay, immediate = false) {
let timeoutId;
return function (...args) {
const context = this;
// 如果是立即执行模式且没有定时器,则立即执行
if (immediate && !timeoutId) {
func.apply(context, args);
}
// 清除之前的定时器
clearTimeout(timeoutId);
// 设置新的定时器
timeoutId = setTimeout(() => {
// 如果不是立即执行模式,则延迟执行
if (!immediate) {
func.apply(context, args);
}
timeoutId = null;
}, delay);
};
}
// 使用示例
const debouncedSearch = debounce(function(query) {
console.log('Searching for:', query);
// 执行搜索逻辑
}, 300);
// 输入框事件处理
document.getElementById('searchInput').addEventListener('input', function(e) {
debouncedSearch(e.target.value);
});
5.1.2 高级防抖实现
javascript
class AdvancedDebounce {
constructor(func, delay, options = {}) {
this.func = func;
this.delay = delay;
this.immediate = options.immediate || false;
this.maxWait = options.maxWait || null;
this.leading = options.leading || false;
this.trailing = options.trailing !== false; // 默认为true
this.timeoutId = null;
this.maxTimeoutId = null;
this.lastCallTime = 0;
this.lastInvokeTime = 0;
this.result = null;
}
invoke(args, context) {
this.lastInvokeTime = Date.now();
this.result = this.func.apply(context, args);
return this.result;
}
leadingEdge(args, context) {
this.lastInvokeTime = Date.now();
this.timeoutId = setTimeout(() => this.trailingEdge(args, context), this.delay);
return this.leading ? this.invoke(args, context) : this.result;
}
remainingWait() {
const timeSinceLastCall = Date.now() - this.lastCallTime;
const timeSinceLastInvoke = Date.now() - this.lastInvokeTime;
const timeWaiting = this.delay - timeSinceLastCall;
return this.maxWait !== null
? Math.min(timeWaiting, this.maxWait - timeSinceLastInvoke)
: timeWaiting;
}
shouldInvoke() {
const timeSinceLastCall = Date.now() - this.lastCallTime;
const timeSinceLastInvoke = Date.now() - this.lastInvokeTime;
return (
this.lastCallTime === 0 ||
timeSinceLastCall >= this.delay ||
timeSinceLastCall < 0 ||
(this.maxWait !== null && timeSinceLastInvoke >= this.maxWait)
);
}
trailingEdge(args, context) {
this.timeoutId = null;
if (this.trailing && this.lastCallTime !== 0) {
return this.invoke(args, context);
}
this.lastCallTime = 0;
return this.result;
}
cancel() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
if (this.maxTimeoutId) {
clearTimeout(this.maxTimeoutId);
}
this.lastCallTime = 0;
this.lastInvokeTime = 0;
this.timeoutId = null;
this.maxTimeoutId = null;
}
flush() {
if (this.timeoutId) {
const args = this.pendingArgs;
const context = this.pendingContext;
this.cancel();
return this.invoke(args, context);
}
return this.result;
}
call(...args) {
const context = this;
this.lastCallTime = Date.now();
this.pendingArgs = args;
this.pendingContext = context;
if (this.shouldInvoke()) {
if (!this.timeoutId) {
return this.leadingEdge(args, context);
}
if (this.maxWait !== null) {
this.maxTimeoutId = setTimeout(() => {
this.trailingEdge(args, context);
}, this.maxWait);
}
}
if (!this.timeoutId) {
this.timeoutId = setTimeout(() => {
this.trailingEdge(args, context);
}, this.remainingWait());
}
return this.result;
}
}
// 使用示例
const advancedDebouncedFunc = new AdvancedDebounce(
function(value) {
console.log('Function executed with:', value);
return value.toUpperCase();
},
300,
{
leading: true,
trailing: true,
maxWait: 1000
}
);
// 模拟频繁调用
setInterval(() => {
const result = advancedDebouncedFunc.call('test');
console.log('Result:', result);
}, 100);
5.2 节流(Throttle)详解
节流是指连续触发事件但是在n秒中只执行一次函数。
5.2.1 基础节流实现
javascript
function throttle(func, delay) {
let timeoutId;
let lastExecTime = 0;
return function (...args) {
const context = this;
const currentTime = Date.now();
if (currentTime - lastExecTime > delay) {
func.apply(context, args);
lastExecTime = currentTime;
} else {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(context, args);
lastExecTime = Date.now();
}, delay - (currentTime - lastExecTime));
}
};
}
// 使用示例
const throttledScroll = throttle(function() {
console.log('Scroll event handled');
// 处理滚动逻辑
}, 100);
window.addEventListener('scroll', throttledScroll);
5.2.2 高级节流实现
javascript
class AdvancedThrottle {
constructor(func, delay, options = {}) {
this.func = func;
this.delay = delay;
this.leading = options.leading !== false; // 默认为true
this.trailing = options.trailing !== false; // 默认为true
this.timeoutId = null;
this.lastCallTime = 0;
this.lastArgs = null;
this.lastContext = null;
this.result = null;
}
invoke(args, context) {
this.lastCallTime = Date.now();
this.result = this.func.apply(context, args);
return this.result;
}
trailingEdge() {
if (this.trailing && this.lastArgs) {
const args = this.lastArgs;
const context = this.lastContext;
this.timeoutId = null;
this.lastArgs = null;
this.lastContext = null;
return this.invoke(args, context);
}
this.timeoutId = null;
return this.result;
}
call(...args) {
const context = this;
const currentTime = Date.now();
const timeSinceLastCall = currentTime - this.lastCallTime;
this.lastArgs = args;
this.lastContext = context;
if (timeSinceLastCall >= this.delay) {
// 超过了延迟时间,可以直接执行
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
this.lastCallTime = currentTime;
this.result = this.func.apply(context, args);
} else if (!this.timeoutId && this.trailing) {
// 设置定时器等待执行
this.timeoutId = setTimeout(() => {
this.trailingEdge();
}, this.delay - timeSinceLastCall);
}
return this.result;
}
cancel() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.lastCallTime = 0;
this.lastArgs = null;
this.lastContext = null;
this.timeoutId = null;
}
flush() {
if (this.timeoutId) {
const args = this.lastArgs;
const context = this.lastContext;
this.cancel();
this.lastCallTime = Date.now();
return this.func.apply(context, args);
}
return this.result;
}
}
// 使用示例
const advancedThrottledFunc = new AdvancedThrottle(
function(x, y) {
console.log('Throttled function called with:', x, y);
return x + y;
},
500,
{
leading: true,
trailing: true
}
);
// 模拟频繁调用
setInterval(() => {
const result = advancedThrottledFunc.call(Math.random(), Math.random());
console.log('Result:', result);
}, 50);
5.3 防抖节流在实际场景中的应用
5.3.1 搜索框优化
javascript
class SearchOptimizer {
constructor(inputElement, searchFunction, options = {}) {
this.inputElement = inputElement;
this.searchFunction = searchFunction;
this.debounceDelay = options.debounceDelay || 300;
this.throttleDelay = options.throttleDelay || 100;
this.init();
}
init() {
// 防抖处理输入事件
this.debouncedInputHandler = debounce(
this.handleInputChange.bind(this),
this.debounceDelay
);
// 节流处理搜索请求
this.throttledSearch = throttle(
this.performSearch.bind(this),
this.throttleDelay
);
this.inputElement.addEventListener('input', this.debouncedInputHandler);
}
handleInputChange(event) {
const query = event.target.value.trim();
if (query.length === 0) {
this.clearResults();
return;
}
if (query.length >= 2) {
this.throttledSearch(query);
}
}
performSearch(query) {
console.log('Performing search for:', query);
// 执行实际的搜索逻辑
this.searchFunction(query)
.then(results => this.displayResults(results))
.catch(error => this.handleError(error));
}
displayResults(results) {
// 显示搜索结果
console.log('Search results:', results);
}
clearResults() {
// 清空搜索结果
console.log('Clearing results');
}
handleError(error) {
console.error('Search error:', error);
}
destroy() {
this.inputElement.removeEventListener('input', this.debouncedInputHandler);
}
}
// 使用示例
const searchInput = document.getElementById('searchInput');
const searchOptimizer = new SearchOptimizer(searchInput, async (query) => {
// 模拟API调用
return new Promise(resolve => {
setTimeout(() => {
resolve([`Result 1 for ${query}`, `Result 2 for ${query}`]);
}, 200);
});
});
5.3.2 窗口大小调整优化
javascript
class ResizeOptimizer {
constructor(handler, options = {}) {
this.handler = handler;
this.debounceDelay = options.debounceDelay || 250;
this.throttleDelay = options.throttleDelay || 100;
this.useDebounce = options.useDebounce !== false; // 默认使用防抖
this.init();
}
init() {
if (this.useDebounce) {
this.optimizedHandler = debounce(
this.handler,
this.debounceDelay
);
} else {
this.optimizedHandler = throttle(
this.handler,
this.throttleDelay
);
}
window.addEventListener('resize', this.optimizedHandler);
}
destroy() {
window.removeEventListener('resize', this.optimizedHandler);
}
}
// 使用示例
const resizeOptimizer = new ResizeOptimizer(() => {
const width = window.innerWidth;
const height = window.innerHeight;
console.log(`Window resized to ${width}x${height}`);
// 执行响应式布局调整
adjustLayout(width, height);
});
function adjustLayout(width, height) {
// 根据窗口大小调整布局
if (width < 768) {
document.body.classList.add('mobile-layout');
document.body.classList.remove('desktop-layout');
} else {
document.body.classList.add('desktop-layout');
document.body.classList.remove('mobile-layout');
}
}
5.3.3 滚动事件优化
javascript
class ScrollOptimizer {
constructor(handler, options = {}) {
this.handler = handler;
this.throttleDelay = options.throttleDelay || 16; // 约60fps
this.debounceDelay = options.debounceDelay || 100;
this.useRAF = options.useRAF !== false; // 默认使用requestAnimationFrame
this.init();
}
init() {
if (this.useRAF) {
this.rafId = null;
this.pendingExecution = false;
this.optimizedHandler = () => {
if (!this.pendingExecution) {
this.pendingExecution = true;
this.rafId = requestAnimationFrame(() => {
this.handler();
this.pendingExecution = false;
});
}
};
} else {
this.optimizedHandler = throttle(
this.handler,
this.throttleDelay
);
}
window.addEventListener('scroll', this.optimizedHandler, { passive: true });
}
destroy() {
window.removeEventListener('scroll', this.optimizedHandler);
if (this.rafId) {
cancelAnimationFrame(this.rafId);
}
}
}
// 使用示例
const scrollOptimizer = new ScrollOptimizer(() => {
const scrollTop = window.pageYOffset;
const scrollHeight = document.documentElement.scrollHeight;
const clientHeight = document.documentElement.clientHeight;
// 计算滚动百分比
const scrollPercent = (scrollTop / (scrollHeight - clientHeight)) * 100;
// 更新进度条
updateProgressBar(scrollPercent);
// 检查是否需要懒加载
checkLazyLoad();
// 处理固定导航栏
handleStickyNavigation(scrollTop);
});
function updateProgressBar(percent) {
const progressBar = document.getElementById('progress-bar');
if (progressBar) {
progressBar.style.width = percent + '%';
}
}
function checkLazyLoad() {
// 实现懒加载逻辑
}
function handleStickyNavigation(scrollTop) {
const navbar = document.getElementById('navbar');
if (navbar) {
if (scrollTop > 100) {
navbar.classList.add('sticky');
} else {
navbar.classList.remove('sticky');
}
}
}
5.4 防抖节流工具库
javascript
// 防抖节流工具库
const ThrottleDebounce = {
// 防抖
debounce(func, delay, options = {}) {
let timeoutId;
let lastArgs;
let lastThis;
let result;
const leading = !!options.leading;
const trailing = options.trailing !== false;
const maxWait = options.maxWait;
function invokeFunc(time) {
const args = lastArgs;
const thisArg = lastThis;
lastArgs = lastThis = undefined;
lastInvokeTime = time;
result = func.apply(thisArg, args);
return result;
}
function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time;
timeoutId = setTimeout(timerExpired, delay);
return leading ? invokeFunc(time) : result;
}
function remainingWait(time) {
const timeSinceLastCall = time - lastCallTime;
const timeSinceLastInvoke = time - lastInvokeTime;
const timeWaiting = delay - timeSinceLastCall;
return maxWait
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting;
}
function shouldInvoke(time) {
const timeSinceLastCall = time - lastCallTime;
const timeSinceLastInvoke = time - lastInvokeTime;
// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return (lastCallTime === undefined || (timeSinceLastCall >= delay) ||
(timeSinceLastCall < 0) || (maxWait && timeSinceLastInvoke >= maxWait));
}
function timerExpired() {
const time = Date.now();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// Restart the timer.
timeoutId = setTimeout(timerExpired, remainingWait(time));
}
function trailingEdge(time) {
timeoutId = undefined;
// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
function cancel() {
if (timeoutId !== undefined) {
clearTimeout(timeoutId);
}
lastInvokeTime = 0;
lastArgs = lastCallTime = lastThis = timeoutId = undefined;
}
function flush() {
return timeoutId === undefined ? result : trailingEdge(Date.now());
}
let lastInvokeTime = 0;
let lastCallTime = 0;
return function(...args) {
const time = Date.now();
const isInvoking = shouldInvoke(time);
lastArgs = args;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
if (timeoutId === undefined) {
return leadingEdge(lastCallTime);
}
if (maxWait !== undefined) {
// Handle invocations in a tight loop.
timeoutId = setTimeout(timerExpired, delay);
return invokeFunc(lastCallTime);
}
}
if (timeoutId === undefined) {
timeoutId = setTimeout(timerExpired, delay);
}
return result;
};
},
// 节流
throttle(func, delay, options = {}) {
let timeoutId, lastArgs, lastThis, result;
let lastCallTime = 0;
let lastInvokeTime = 0;
const leading = !!options.leading;
const trailing = !!options.trailing;
function invokeFunc(time) {
const args = lastArgs;
const thisArg = lastThis;
lastArgs = lastThis = undefined;
lastInvokeTime = time;
result = func.apply(thisArg, args);
return result;
}
function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time;
timeoutId = setTimeout(timerExpired, delay);
return leading ? invokeFunc(time) : result;
}
function remainingWait(time) {
const timeSinceLastCall = time - lastCallTime;
const timeSinceLastInvoke = time - lastInvokeTime;
const timeWaiting = delay - timeSinceLastCall;
return timeSinceLastCall < 0 ? 0 : timeWaiting;
}
function shouldInvoke(time) {
const timeSinceLastCall = time - lastCallTime;
const timeSinceLastInvoke = time - lastInvokeTime;
// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return (lastCallTime === undefined || (timeSinceLastCall >= delay) ||
(timeSinceLastCall < 0));
}
function timerExpired() {
const time = Date.now();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// Restart the timer.
timeoutId = setTimeout(timerExpired, remainingWait(time));
}
function trailingEdge(time) {
timeoutId = undefined;
// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
function cancel() {
if (timeoutId !== undefined) {
clearTimeout(timeoutId);
}
lastInvokeTime = 0;
lastArgs = lastCallTime = lastThis = timeoutId = undefined;
}
function flush() {
return timeoutId === undefined ? result : trailingEdge(Date.now());
}
return function(...args) {
const time = Date.now();
const isInvoking = shouldInvoke(time);
lastArgs = args;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
if (timeoutId === undefined) {
return leadingEdge(lastCallTime);
}
timeoutId = setTimeout(timerExpired, delay);
return invokeFunc(lastCallTime);
}
if (timeoutId === undefined) {
timeoutId = setTimeout(timerExpired, delay);
}
return result;
};
}
};
// 导出工具库
if (typeof module !== 'undefined' && module.exports) {
module.exports = ThrottleDebounce;
} else if (typeof window !== 'undefined') {
window.ThrottleDebounce = ThrottleDebounce;
}
总结
本文详细介绍了前端开发中的五项关键技术:
-
虚拟滚动:通过只渲染可视区域内的元素来优化长列表性能,显著减少DOM节点数量,提升渲染效率。
-
时间切片:将长时间运行的任务分解为小片段执行,避免阻塞主线程,保持页面响应性。
-
Web Workers多线程:利用后台线程执行计算密集型任务,释放主线程压力,提升用户体验。
-
内存泄漏检测和处理:识别和预防常见的内存泄漏场景,建立完善的生命周期管理机制。
-
防抖节流高级应用:合理控制函数执行频率,优化高频事件处理,提升应用性能。
这些技术在现代前端开发中具有重要意义,掌握它们能够帮助开发者构建更高性能、更流畅的Web应用。在实际项目中,应该根据具体需求选择合适的技术方案,并结合性能监控工具持续优化应用性能。