避免长任务(Long Tasks)的策略
理解长任务(Long Tasks)的定义
长任务是指主线程上执行时间超过50毫秒的任务。浏览器将这类任务标记为可能阻塞用户交互的潜在问题。当长任务频繁出现时,会导致页面响应迟缓、动画卡顿等问题。现代浏览器通过Long Tasks API可以检测这些任务:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('长任务 detected:', entry);
}
});
observer.observe({entryTypes: ['longtask']});
代码拆分与懒加载
将大型JavaScript包拆分为更小的模块是减少长任务的有效方法。使用动态导入实现按需加载:
// 传统方式
import { heavyModule } from './heavyModule';
// 动态导入方式
button.addEventListener('click', async () => {
const { heavyFunction } = await import('./heavyModule');
heavyFunction();
});
对于React应用,可以使用React.lazy和Suspense:
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
任务分解与时间切片
将大型任务分解为多个小任务,使用setTimeout或requestIdleCallback进行分片处理:
function processLargeArray(array) {
let index = 0;
function processChunk() {
const start = performance.now();
while (index < array.length && performance.now() - start < 10) {
// 处理单个项目
processItem(array[index++]);
}
if (index < array.length) {
// 使用setTimeout让出主线程
setTimeout(processChunk, 0);
}
}
processChunk();
}
对于React应用,可以使用时间切片API:
function MyComponent({ items }) {
return (
<ul>
{items.map(item => (
<React.unstable_SuspenseList revealOrder="forwards">
<Item key={item.id} item={item} />
</React.unstable_SuspenseList>
))}
</ul>
);
}
Web Worker的运用
将CPU密集型任务转移到Web Worker中可以显著减少主线程负担:
// 主线程代码
const worker = new Worker('worker.js');
worker.postMessage({ data: largeDataSet });
worker.onmessage = (event) => {
console.log('结果:', event.data);
};
// worker.js
self.onmessage = (event) => {
const result = heavyComputation(event.data);
self.postMessage(result);
};
优化动画性能
使用CSS动画和transform属性代替JavaScript动画:
/* 优化前 */
.element {
left: 0;
transition: left 0.3s ease;
}
/* 优化后 */
.element {
transform: translateX(0);
transition: transform 0.3s ease;
}
对于复杂动画,使用requestAnimationFrame:
function animate() {
// 动画逻辑
element.style.transform = `translateX(${position}px)`;
if (position < target) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
虚拟列表优化长列表渲染
对于大型列表,只渲染可视区域内的项目:
function VirtualList({ items, itemHeight, containerHeight }) {
const [scrollTop, setScrollTop] = useState(0);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
items.length - 1,
startIndex + Math.ceil(containerHeight / itemHeight)
);
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
>
<div style={{ height: items.length * itemHeight }}>
{items.slice(startIndex, endIndex + 1).map((item, index) => (
<div
key={item.id}
style={{
height: itemHeight,
position: 'absolute',
top: (startIndex + index) * itemHeight
}}
>
{item.content}
</div>
))}
</div>
</div>
);
}
减少样式计算复杂度
避免使用通配选择器和深层嵌套:
/* 不推荐 */
div * {
color: red;
}
/* 推荐 */
.specific-class {
color: red;
}
使用BEM等命名约定减少选择器复杂度:
/* BEM示例 */
.block {}
.block__element {}
.block--modifier {}
优化事件处理
使用事件委托减少事件监听器数量:
// 不推荐
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', handleClick);
});
// 推荐
document.querySelector('.container').addEventListener('click', (e) => {
if (e.target.matches('.item')) {
handleClick(e);
}
});
对于频繁触发的事件(如scroll、resize),使用防抖或节流:
function debounce(func, delay) {
let timeout;
return function() {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, arguments), delay);
};
}
window.addEventListener('resize', debounce(handleResize, 200));
内存管理优化
及时清理不再需要的引用和事件监听器:
class Component {
constructor() {
this.data = fetchData();
window.addEventListener('resize', this.handleResize);
}
// 必须实现清理逻辑
cleanup() {
this.data = null;
window.removeEventListener('resize', this.handleResize);
}
}
避免内存泄漏的常见模式:
// 闭包中的引用
function createHeavyObject() {
const largeObject = createLargeObject();
return function() {
// 意外保留了largeObject的引用
console.log('操作');
};
}
// 更好的方式
function createLightweightClosure() {
const neededData = extractNeededData(createLargeObject());
return function() {
console.log(neededData);
};
}
服务端渲染优化
对于复杂的初始渲染,考虑服务端渲染:
// Express服务器示例
app.get('/', (req, res) => {
const appContent = ReactDOMServer.renderToString(<App />);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>SSR示例</title>
</head>
<body>
<div id="root">${appContent}</div>
<script src="/client.bundle.js"></script>
</body>
</html>
`);
});
预加载关键资源
使用rel="preload"提前加载关键资源:
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="main.js" as="script">
对于字体文件:
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
避免强制同步布局
批量读取样式属性,避免布局抖动:
// 不推荐 - 导致强制同步布局
function resizeAllItems() {
const items = document.querySelectorAll('.item');
for (let i = 0; i < items.length; i++) {
items[i].style.height = items[i].offsetWidth + 'px';
}
}
// 推荐 - 先读取后写入
function resizeAllItemsOptimized() {
const items = document.querySelectorAll('.item');
const widths = [];
// 批量读取
for (let i = 0; i < items.length; i++) {
widths[i] = items[i].offsetWidth;
}
// 批量写入
for (let i = 0; i < items.length; i++) {
items[i].style.height = widths[i] + 'px';
}
}
使用高效的DOM操作方法
减少DOM操作次数:
// 不推荐
for (let i = 0; i < 100; i++) {
document.body.appendChild(document.createElement('div'));
}
// 推荐 - 使用DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
fragment.appendChild(document.createElement('div'));
}
document.body.appendChild(fragment);
优化第三方脚本加载
异步加载非关键第三方脚本:
<script src="analytics.js" async defer></script>
或者使用Intersection Observer延迟加载:
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
const script = document.createElement('script');
script.src = 'analytics.js';
document.body.appendChild(script);
observer.disconnect();
}
});
observer.observe(document.querySelector('.footer'));
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:事件委托优化事件处理
下一篇:代码分割与懒加载实现