阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 避免长任务(Long Tasks)的策略

避免长任务(Long Tasks)的策略

作者:陈川 阅读数:30126人阅读 分类: 性能优化

理解长任务(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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌