Strategies to avoid Long Tasks
Understanding the Definition of Long Tasks
Long Tasks refer to tasks executed on the main thread that exceed 50 milliseconds in duration. Browsers flag such tasks as potential issues that may block user interactions. Frequent occurrences of long tasks can lead to sluggish page responses, animation stuttering, and other performance problems. Modern browsers can detect these tasks using the Long Tasks API:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Long task detected:', entry);
}
});
observer.observe({entryTypes: ['longtask']});
Code Splitting and Lazy Loading
Splitting large JavaScript bundles into smaller modules is an effective way to reduce long tasks. Use dynamic imports for on-demand loading:
// Traditional approach
import { heavyModule } from './heavyModule';
// Dynamic import approach
button.addEventListener('click', async () => {
const { heavyFunction } = await import('./heavyModule');
heavyFunction();
});
For React applications, use React.lazy
and Suspense
:
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
Task Decomposition and Time Slicing
Break down large tasks into smaller chunks and use setTimeout
or requestIdleCallback
for incremental processing:
function processLargeArray(array) {
let index = 0;
function processChunk() {
const start = performance.now();
while (index < array.length && performance.now() - start < 10) {
// Process individual items
processItem(array[index++]);
}
if (index < array.length) {
// Yield the main thread with setTimeout
setTimeout(processChunk, 0);
}
}
processChunk();
}
For React applications, use the Time Slicing API:
function MyComponent({ items }) {
return (
<ul>
{items.map(item => (
<React.unstable_SuspenseList revealOrder="forwards">
<Item key={item.id} item={item} />
</React.unstable_SuspenseList>
))}
</ul>
);
}
Utilizing Web Workers
Offload CPU-intensive tasks to Web Workers to significantly reduce main thread load:
// Main thread code
const worker = new Worker('worker.js');
worker.postMessage({ data: largeDataSet });
worker.onmessage = (event) => {
console.log('Result:', event.data);
};
// worker.js
self.onmessage = (event) => {
const result = heavyComputation(event.data);
self.postMessage(result);
};
Optimizing Animation Performance
Use CSS animations and transform
properties instead of JavaScript animations:
/* Before optimization */
.element {
left: 0;
transition: left 0.3s ease;
}
/* After optimization */
.element {
transform: translateX(0);
transition: transform 0.3s ease;
}
For complex animations, use requestAnimationFrame
:
function animate() {
// Animation logic
element.style.transform = `translateX(${position}px)`;
if (position < target) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
Virtual List Optimization for Long Lists
For large lists, render only items within the visible area:
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>
);
}
Reducing Style Calculation Complexity
Avoid wildcard selectors and deep nesting:
/* Not recommended */
div * {
color: red;
}
/* Recommended */
.specific-class {
color: red;
}
Use naming conventions like BEM to reduce selector complexity:
/* BEM example */
.block {}
.block__element {}
.block--modifier {}
Optimizing Event Handling
Use event delegation to reduce the number of event listeners:
// Not recommended
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', handleClick);
});
// Recommended
document.querySelector('.container').addEventListener('click', (e) => {
if (e.target.matches('.item')) {
handleClick(e);
}
});
For frequently triggered events (e.g., scroll
, resize
), use debouncing or throttling:
function debounce(func, delay) {
let timeout;
return function() {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, arguments), delay);
};
}
window.addEventListener('resize', debounce(handleResize, 200));
Memory Management Optimization
Clean up unused references and event listeners promptly:
class Component {
constructor() {
this.data = fetchData();
window.addEventListener('resize', this.handleResize);
}
// Must implement cleanup logic
cleanup() {
this.data = null;
window.removeEventListener('resize', this.handleResize);
}
}
Avoid common memory leak patterns:
// References in closures
function createHeavyObject() {
const largeObject = createLargeObject();
return function() {
// Accidentally retains reference to largeObject
console.log('Operation');
};
}
// Better approach
function createLightweightClosure() {
const neededData = extractNeededData(createLargeObject());
return function() {
console.log(neededData);
};
}
Server-Side Rendering Optimization
For complex initial renders, consider server-side rendering:
// Express server example
app.get('/', (req, res) => {
const appContent = ReactDOMServer.renderToString(<App />);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>SSR Example</title>
</head>
<body>
<div id="root">${appContent}</div>
<script src="/client.bundle.js"></script>
</body>
</html>
`);
});
Preloading Critical Resources
Use rel="preload"
to load critical resources early:
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="main.js" as="script">
For font files:
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
Avoiding Forced Synchronous Layouts
Batch style property reads to avoid layout thrashing:
// Not recommended - causes forced synchronous layout
function resizeAllItems() {
const items = document.querySelectorAll('.item');
for (let i = 0; i < items.length; i++) {
items[i].style.height = items[i].offsetWidth + 'px';
}
}
// Recommended - read first, then write
function resizeAllItemsOptimized() {
const items = document.querySelectorAll('.item');
const widths = [];
// Batch reads
for (let i = 0; i < items.length; i++) {
widths[i] = items[i].offsetWidth;
}
// Batch writes
for (let i = 0; i < items.length; i++) {
items[i].style.height = widths[i] + 'px';
}
}
Using Efficient DOM Manipulation Methods
Minimize DOM operations:
// Not recommended
for (let i = 0; i < 100; i++) {
document.body.appendChild(document.createElement('div'));
}
// Recommended - use DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
fragment.appendChild(document.createElement('div'));
}
document.body.appendChild(fragment);
Optimizing Third-Party Script Loading
Load non-critical third-party scripts asynchronously:
<script src="analytics.js" async defer></script>
Or use Intersection Observer for lazy loading:
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
上一篇:事件委托优化事件处理
下一篇:代码分割与懒加载实现