前端性能优化-第三篇(JavaScript执行优化)

摘要: JavaScript是单线程的,低效的JavaScript代码会阻塞主线程,导致页面卡顿和无响应。本篇聚焦于编写高效的JavaScript,让主线程快速"空闲"出来,确保交互的流畅性。

核心内容

1. 避免长任务(Long Tasks)与任务分解

任何在主线程上连续执行超过 50毫秒 的任务都被视为"长任务",它会阻塞用户交互(如点击、滚动)的响应。

优化策略:任务分解 。将一个大任务拆分成多个小任务,通过 setTimeoutsetIntervalrequestIdleCallback 分片执行。

示例:处理一个大型数组

javascript 复制代码
// 优化前:一个长任务
function processAllItems(items) {
  for (let i = 0; i < items.length; i++) {
    // 假设这是一个很耗时的处理
    heavyProcessing(items[i]);
  }
}

// 优化后:分解任务,避免阻塞主线程
function processItemsAsync(items) {
  let index = 0;
  
  function processNextChunk() {
    const startTime = performance.now();
    // 每次处理一小批,例如50个
    while (index < items.length && performance.now() - startTime < 50) {
      heavyProcessing(items[index]);
      index++;
    }
    
    if (index < items.length) {
      // 如果还有剩余项,将下一个分片放到任务队列末尾
      setTimeout(processNextChunk, 0);
    }
  }
  
  processNextChunk();
}

2. 防抖(Debounce)与节流(Throttle)

这两个技术是控制函数执行频率的利器。

防抖 (Debounce) :事件停止触发后,延迟一段时间再执行。最后一次操作才真正执行

  • 适用场景 :搜索框输入联想、窗口 resize 结束后的布局调整。

节流 (Throttle) :在一定时间间隔内,只执行一次。按固定频率执行

  • 适用场景 :页面滚动 scroll、鼠标移动 mousemove、频繁点击按钮。

示例实现:

javascript 复制代码
// 防抖实现
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// 节流实现
function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// 使用示例
const searchInput = document.getElementById('search');
// 用户停止输入300ms后才执行搜索
searchInput.addEventListener('input', debounce(function(e) {
  console.log('发起搜索请求:', e.target.value);
}, 300));

// 滚动时,每100ms最多执行一次
window.addEventListener('scroll', throttle(function() {
  console.log('计算位置,懒加载图片');
}, 100));

3. 使用 requestAnimationFrame 进行动画

requestAnimationFrame (rAF) 是浏览器为动画专门提供的API,它会在下一次浏览器重绘之前执行回调函数,保证动画与浏览器的刷新率同步(通常是60fps)。

示例:一个平滑的动画

javascript 复制代码
// 使用 rAF 的流畅动画
function animateWithRAF(element, duration) {
  const start = performance.now();
  const from = 0;
  const to = 500;
  
  function step(timestamp) {
    const progress = Math.min((timestamp - start) / duration, 1);
    element.style.transform = `translateX(${from + (to - from) * progress}px)`;
    
    if (progress < 1) {
      requestAnimationFrame(step);
    }
  }
  
  requestAnimationFrame(step);
}

// 对比:不要这样写(使用setInterval的卡顿动画)
function animateWithInterval(element, duration) {
  const start = Date.now();
  const from = 0;
  const to = 500;
  
  const timer = setInterval(() => {
    const progress = Math.min((Date.now() - start) / duration, 1);
    element.style.left = `${from + (to - from) * progress}px`;
    
    if (progress >= 1) {
      clearInterval(timer);
    }
  }, 1000 / 60); // 试图模拟60fps
}

关键区别rAF 在浏览器渲染前执行,而 setInterval 的时间不精确,可能在帧中间执行,导致丢帧或过度渲染。

4. Web Workers 处理复杂计算

Web Workers 允许在后台线程中运行脚本,不会阻塞主线程。适合处理大量数据计算、图像处理等CPU密集型任务。

示例:使用Web Worker进行费时的计算

javascript 复制代码
// 主线程代码 main.js
const worker = new Worker('worker.js');

// 向Worker发送数据
worker.postMessage({ data: largeArray });

// 接收Worker返回的结果
worker.onmessage = function(e) {
  console.log('计算结果:', e.data.result);
  // 更新UI
};

worker.onerror = function(error) {
  console.error('Worker错误:', error);
};

// Worker代码 worker.js
self.onmessage = function(e) {
  const data = e.data.data;
  // 执行复杂的计算(不会阻塞主线程!)
  const result = complexCalculation(data);
  
  // 将结果发送回主线程
  self.postMessage({ result: result });
};

function complexCalculation(data) {
  // 模拟复杂计算
  return data.map(item => item * 2).filter(item => item > 1000);
}

5. 事件委托(Event Delegation)

利用事件冒泡机制,将事件监听器绑定在父元素上,而不是每个子元素上。

示例:动态列表的事件处理

javascript 复制代码
// 不好的做法:为每个列表项绑定监听器
document.querySelectorAll('.item').forEach(item => {
  item.addEventListener('click', function() {
    console.log('点击了:', this.textContent);
  });
});

// 优化:使用事件委托,只需一个监听器
document.getElementById('list').addEventListener('click', function(e) {
  // 检查点击的是否是.item元素
  if (e.target && e.target.matches('.item')) {
    console.log('点击了:', e.target.textContent);
  }
});

// 优点:
// 1. 内存占用更少(只有一个监听器)
// 2. 对动态添加的子元素自动生效(无需重新绑定)
// 3. 初始化和销毁更高效

6. 代码层面优化

缓存查询结果和计算值

javascript 复制代码
// 不好的做法:每次循环都查询DOM和计算长度
for (let i = 0; i < document.querySelectorAll('.items').length; i++) {
  // ...
}

// 优化:缓存查询结果和长度
const items = document.querySelectorAll('.items');
const length = items.length;
for (let i = 0; i < length; i++) {
  // ...
}

使用更高效的选择器

javascript

dart 复制代码
// 较慢:需要遍历更多元素
document.querySelector('div .item .name');

// 较快:ID选择器最快
document.getElementById('myElement');

// 较快:类选择器配合querySelectorAll
document.querySelectorAll('.items');

避免在循环中修改DOM

javascript 复制代码
// 不好的做法:每次循环都修改DOM(导致多次回流)
const list = document.getElementById('list');
data.forEach(item => {
  const li = document.createElement('li');
  li.textContent = item.name;
  list.appendChild(li); // 每次append都会可能引发回流
});

// 优化:使用DocumentFragment批量操作
const list = document.getElementById('list');
const fragment = document.createDocumentFragment();

data.forEach(item => {
  const li = document.createElement('li');
  li.textContent = item.name;
  fragment.appendChild(li);
});

list.appendChild(fragment); // 只引发一次回流

7. 使用性能API进行监控

利用 Performance API 来测量代码执行时间,找到性能瓶颈。

示例:测量函数执行时间

javascript 复制代码
// 使用 Performance Mark 和 Measure
function measurePerf() {
  // 开始标记
  performance.mark('start-processing');
  
  // 执行需要测量的代码
  heavyProcessing();
  
  // 结束标记
  performance.mark('end-processing');
  
  // 测量两个标记之间的时间
  performance.measure('processing-duration', 'start-processing', 'end-processing');
  
  // 获取测量结果
  const measures = performance.getEntriesByName('processing-duration');
  console.log(`处理耗时: ${measures[0].duration} 毫秒`);
  
  // 清理标记
  performance.clearMarks();
  performance.clearMeasures();
}

总结 :JavaScript执行优化的核心思想是 "减少主线程负担,避免长时间阻塞" 。通过任务分解、控制执行频率、利用后台线程、优化事件处理和代码逻辑,可以显著提升应用的响应速度和用户体验。

相关推荐
yuzhiboyouye7 小时前
前端架构师,是架构什么
前端·架构
全马必破三7 小时前
Buffer:Node.js 里处理二进制数据的 “小工具”
前端·node.js
web安全工具库7 小时前
Linux 高手进阶:Vim 核心模式与分屏操作详解
linux·运维·服务器·前端·数据库
一枚前端小能手7 小时前
🔥 SSR服务端渲染实战技巧 - 从零到一构建高性能全栈应用
前端·javascript
Komorebi_99997 小时前
Vue3 provide/inject 详细组件关系说明
前端·javascript·vue.js
用户1412501665277 小时前
一文彻底掌握 ECharts:从配置解读到实战应用
前端
LRH7 小时前
React 架构设计:从 stack reconciler 到 fiber reconciler 的演进
前端
VIjolie7 小时前
文档/会议类应用的协同同步机制(OT/CRDT简要理解)
前端
不一样的少年_7 小时前
【前端效率工具】:告别右键另存,不到 50 行代码一键批量下载网页图片
前端·javascript·浏览器