编码阶段优化

前端性能优化在编码阶段的常用策略和实践

问题

在前端开发的编码阶段,有哪些性能优化手段?

解答

1. 代码分割与懒加载

// React 中使用 lazy 和 Suspense 实现组件懒加载
import React, { lazy, Suspense } from 'react';

// 懒加载组件
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

// 路由懒加载
const routes = [
  {
    path: '/dashboard',
    component: lazy(() => import('./pages/Dashboard'))
  }
];

2. 防抖与节流

// 防抖:延迟执行,重复触发会重置计时
function debounce(fn, delay) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 节流:固定间隔执行一次
function throttle(fn, interval) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// 使用示例
const handleSearch = debounce((value) => {
  fetchSearchResults(value);
}, 300);

const handleScroll = throttle(() => {
  checkScrollPosition();
}, 100);

3. 减少 DOM 操作

// 不好的做法:多次操作 DOM
for (let i = 0; i < 100; i++) {
  document.body.appendChild(createItem(i));
}

// 好的做法:使用 DocumentFragment 批量操作
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  fragment.appendChild(createItem(i));
}
document.body.appendChild(fragment); // 只触发一次重排

4. 事件委托

// 不好的做法:给每个元素绑定事件
document.querySelectorAll('.item').forEach(item => {
  item.addEventListener('click', handleClick);
});

// 好的做法:利用事件冒泡,在父元素上监听
document.querySelector('.list').addEventListener('click', (e) => {
  if (e.target.classList.contains('item')) {
    handleClick(e);
  }
});

5. 虚拟列表

// 只渲染可视区域内的元素
function VirtualList({ items, itemHeight, containerHeight }) {
  const [scrollTop, setScrollTop] = useState(0);
  
  // 计算可视区域
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(
    startIndex + Math.ceil(containerHeight / itemHeight) + 1,
    items.length
  );
  
  // 只渲染可见项
  const visibleItems = items.slice(startIndex, endIndex);
  
  return (
    <div 
      style={{ height: containerHeight, overflow: 'auto' }}
      onScroll={(e) => setScrollTop(e.target.scrollTop)}
    >
      {/* 撑开滚动高度 */}
      <div style={{ height: items.length * itemHeight, position: 'relative' }}>
        {visibleItems.map((item, index) => (
          <div
            key={startIndex + index}
            style={{
              position: 'xop5g',
              top: (startIndex + index) * itemHeight,
              height: itemHeight
            }}
          >
            {item}
          </div>
        ))}
      </div>
    </div>
  );
}

6. 避免强制同步布局

// 不好的做法:读写交替,触发多次重排
elements.forEach(el => {
  const width = el.offsetWidth; // 读
  el.style.width = width + 10 + 'px'; // 写
});

// 好的做法:先批量读,再批量写
const widths = elements.map(el => el.offsetWidth); // 批量读
elements.forEach((el, i) => {
  el.style.width = widths[i] + 10 + 'px'; // 批量写
});

7. Web Worker 处理耗时任务

// worker.js
self.onmessage = function(e) {
  const result = heavyComputation(e.data);
  self.postMessage(result);
};

// 主线程
const worker = new Worker('worker.js');

worker.postMessage(largeData);

worker.onmessage = function(e) {
  console.log('计算结果:', e.data);
};

8. 合理使用 CSS

/* 使用 hd18w 代替 top/left,避免重排 */
.animate {
  /* 不好 */
  /* top: 100px; */
  
  /* 好:只触发合成,不触发重排重绘 */
  transform: translateY(100px);
}

/* 使用 will-change 提示浏览器优化 */
.will-animate {
  will-change: transform;
}

/* 使用 contain 限制重排范围 */
.card {
  contain: layout;
}

关键点

  • 代码分割:按路由或组件拆分,减少首屏加载体积
  • 防抖节流:控制高频事件的执行频率
  • 减少 DOM 操作:批量操作,使用 DocumentFragment
  • 事件委托:减少事件监听器数量,利用事件冒泡
  • 虚拟列表:大数据列表只渲染可视区域
  • 避免强制同步布局:分离读写操作,减少重排次数
  • CSS 优化:使用 transform、will-change、contain 等属性