背景

        由于DOM树的构建和JS代码的执行都要在主进程(渲染进程)上进行,所以当JS代码执行时间过长时(任务执行时长 > 50ms)即出现长任务时会导致页面卡顿掉帧现象

一、优化 JavaScript 执行效率

1、根据任务管理策略拆分长任务

function main(){
    task1();
    task2();
    task3();
    task4();
    task5();
}

2、手动推迟代码执行

将长任务(执行时间 > 50ms)拆分为小任务,用requestIdleCallbacksetTimeout分片执行:

// 长任务拆分示例
const tasks = [/* 大量待处理任务 */];
const processTask = () => {
  const start = performance.now();
  // 每次处理一部分,确保执行时间不超过20ms
  while (tasks.length && performance.now() - start < 20) {
    const task = tasks.shift();
    process(task); // 处理单个小任务
  }
  if (tasks.length) {
    requestIdleCallback(processTask); // 利用空闲时间继续执行
  }
};
requestIdleCallback(processTask);

3、使用调度器API(有兼容问题)

调度器API提供一个postTask()函数,帮助浏览器确认任务优先级

postTask() API有三个优先级:

  1. 'user-blocking'
    最高优先级,用于处理用户当前正在交互的任务,如输入响应、动画更新等,需要立即执行以保证流畅的用户体验。

  2. 'user-visible'
    中等优先级,用于处理用户可见的任务,如页面内容渲染、数据更新等,这些任务不需要立即响应但对用户可见性有影响。

  3. 'background'
    最低优先级,用于处理后台任务,如数据同步、日志记录等,这些任务不紧急且用户通常不会立即感知到。

function main(){

    scheduler.postTask(task1, {priority: 'user-blocking'});

    scheduler.postTask(task2, {priority: 'user-visible'});
    
    scheduler.postTask(task3, {priority: 'background'});


}

4、减少不必要的 JavaScript

  • 移除未使用的代码(利用tree-shaking、代码分析工具如webpack-bundle-analyzer)。
  • 避免加载冗余库,例如仅需日期格式化时,用轻量库date-fns替代完整的moment.js
  • 拆分大文件,通过代码分割React.lazydynamic import)实现按需加载。

二、使用 Web Worker 隔离计算密集型任务

     Web Worker 允许在后台线程执行脚本,完全不阻塞主线程,适合处理:

  • 大数据解析(如 CSV/JSON 处理)、复杂计算(图表渲染、数据可视化)
  • 图片 / 视频处理、加密解密等耗时操作
// 主线程
const worker = new Worker('data-processor.js');
// 发送数据给Worker
worker.postMessage(largeDataset);
// 接收处理结果
worker.onmessage = (e) => {
  console.log('处理结果:', e.data);
};

// data-processor.js(Worker线程)
self.onmessage = (e) => {
  const result = heavyComputation(e.data); // 密集计算
  self.postMessage(result); // 发送结果回主线程
};

三、优化 DOM 操作与渲染

DOM 操作是主线程的高开销任务,需减少不必要的渲染压力:

  1. 减少 DOM 节点数量

    • 避免嵌套过深的 DOM 结构(推荐深度≤6 层),减少样式计算和布局成本。
    • 用虚拟列表(如react-window)处理长列表,只渲染可视区域内的元素。
  2. 批量 DOM 操作

    • 避免频繁的 DOM 增删改,使用DocumentFragment或离线 DOM(如先修改display: none的元素)批量处理:
    • const fragment = document.createDocumentFragment();
      // 批量添加节点到fragment(不会触发重绘)
      items.forEach(item => {
        const div = document.createElement('div');
        div.textContent = item;
        fragment.appendChild(div);
      });
      // 一次性插入DOM
      container.appendChild(fragment);

     3.避免强制同步布局

                浏览器会批量执行布局计算,若在修改样式后立即读取布局属性(如offsetHeightgetBoundingClientRect),会强制浏览器立即计算布局,导致性能损耗:

// 坏例子:强制同步布局
elements.forEach(el => {
  el.style.width = '100px'; // 修改样式
  const height = el.offsetHeight; // 立即读取,触发强制布局
});

// 好例子:先读取,后修改
const heights = elements.map(el => el.offsetHeight); // 批量读取
elements.forEach((el, i) => {
  el.style.width = `${heights[i]}px`; // 批量修改
});

     4.优化样式与动画

  • 优先使用transformopacity实现动画(由 compositor 线程处理,不阻塞主线程)。
  • 避免使用box-shadowfilter等属性(会触发频繁重绘)。

四、优化资源加载与解析

  1. 延迟加载非关键资源

    • 对非首屏图片、视频使用loading="lazy"属性延迟加载。
    • 异步加载第三方脚本(如广告、社交插件),添加asyncdefer属性:
      <script src="third-party.js" async></script> <!-- 下载完成后立即执行(顺序不定) -->
      <script src="analytics.js" defer></script> <!-- 文档解析完成后按顺序执行 -->
      
  2. 预编译与缓存

    • 利用link rel="preload"预加载关键资源(如字体、核心 JS),提前加载但不立即执行。
    • 合理设置 HTTP 缓存(Cache-Control),减少重复下载和解析。
  3. 减少解析阻塞

    • 将非关键 CSS 放到<link rel="stylesheet" media="print" onload="this.media='all'">,延迟非首屏样式解析。
    • 内联首屏必要 CSS(Critical CSS),避免外部 CSS 阻塞渲染。
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐