JavaScript 性能优化那点事儿:写给真干活的人看

写这篇文章的起因很简单:前阵子项目上线后,老板说页面打开慢、卡,搞得我天天加班排查性能问题。后来一通优化下来,页面确实快了不少。现在趁有空,把这段经历总结下来,也算给自己留个笔记,给各位踩坑的兄弟们一点参考。


一、性能问题的常见“表象”

先别谈优化技巧,咱得搞清楚一个事儿:你到底遇到的是什么样的性能问题?

通常来说,前端的性能瓶颈有这么几类:

  1. 页面加载慢:比如打开首页要等好几秒。

  2. 交互卡顿:点击按钮没反应、拖动不流畅。

  3. 内存泄漏:用久了页面越来越卡,甚至崩溃。

  4. 资源太大了:JS 文件一个几 MB,图片太高清。

如果你连这些问题都不明确,就瞎优化,那不是在救火,是在添乱。


二、首屏加载优化:别让用户等太久

1. 代码分包(Code Splitting)

很多项目都是一个 bundle.js,动不动几 MB。你不分包,浏览器就要一次性下载全部代码,慢得很自然。

怎么解决?

如果你用的是 Webpack,配合动态引入(import())就能实现懒加载:

// 原来的写法
import BigComponent from './BigComponent';

// 改成动态引入
const BigComponent = () => import('./BigComponent');

配合路由切分、组件懒加载,首屏文件能瘦不少。

2. 懒加载图片和资源

用户没滚到底下,图片没必要全加载吧?可以用 loading="lazy"


或者结合 IntersectionObserver 自己写懒加载逻辑,兼容性更强。

3. 用好缓存

静态资源可以加上强缓存(Cache-Control、ETag),避免每次都重新加载。Webpack 打包时可以加上 [contenthash] 来做文件指纹:

output: {
  filename: '[name].[contenthash].js'
}

这样一改动才更新,其他文件都走缓存,省流量也快。


三、运行时优化:别让用户操作时卡顿

1. 减少 DOM 操作

直接操作 DOM 比较重,尤其是循环创建大量元素,建议用批处理虚拟 DOM

比如原来是:

for (let i = 0; i < 1000; i++) {
  const div = document.createElement('div');
  div.innerText = i;
  document.body.appendChild(div); // 每次都插入 DOM
}

改成这样会好点:

const frag = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const div = document.createElement('div');
  div.innerText = i;
  frag.appendChild(div);
}
document.body.appendChild(frag); // 一次插入

2. 减少重排重绘

样式变化会触发浏览器的重排(Reflow)和重绘(Repaint),能合并就合并。

比如:

// 差的做法
el.style.width = '100px';
el.style.height = '100px';
el.style.color = 'red';

可以合并写成:

el.style.cssText = 'width:100px;height:100px;color:red';

或者直接加一个类名:

el.classList.add('active-style');

3. 节流与防抖

有些操作(比如 resizescrollinput)触发频率太高,CPU 直接拉满。用**节流(throttle)防抖(debounce)**处理一下。

// 简单防抖
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// 简单节流
function throttle(fn, delay) {
  let last = 0;
  return function (...args) {
    const now = Date.now();
    if (now - last > delay) {
      last = now;
      fn.apply(this, args);
    }
  };
}

四、异步操作和长任务的拆分

1. 拆分耗时任务

如果 JS 主线程跑了个死循环,页面会直接卡死。比如这样写法就很危险:

for (let i = 0; i < 1e9; i++) {
  doSomething(i);
}

可以拆成异步块处理:

let i = 0;
function run() {
  const chunk = 10000;
  const now = performance.now();
  while (i < 1e9 && performance.now() - now < 16) {
    doSomething(i);
    i++;
  }
  if (i < 1e9) {
    requestAnimationFrame(run);
  }
}
run();

这样主线程还能喘口气。

2. 用 Web Worker

如果你真的要处理计算密集型任务(比如图片处理、加密解密),可以用 Web Worker 把任务丢到子线程:

const worker = new Worker('worker.js');
worker.postMessage({ type: 'calc', data: bigData });

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

五、内存优化:让页面跑得久也不卡

1. 清除不再使用的引用

比如定时器、事件监听器,如果页面销毁了但还引用着,就内存泄漏了。

// BAD
window.addEventListener('scroll', handler);

// 页面销毁时忘了清除

改成:

window.addEventListener('scroll', handler);

// 销毁时清除
window.removeEventListener('scroll', handler);

2. 使用 WeakMap/WeakSet 管理引用

当你需要缓存一些 DOM 或对象,但又不想干扰垃圾回收,可以用 WeakMap

const cache = new WeakMap();

function cacheData(el, data) {
  cache.set(el, data);
}

// 不手动清理,el 被销毁后会自动清掉

六、工具推荐:别瞎猜,测了才知道

  1. Chrome DevTools:老朋友了,Performance 面板看时间轴,Memory 看堆快照,Network 看资源加载。

  2. Lighthouse:可以做个页面体检,看看哪些点性能差。

  3. Web Vitals:Google 推的几个核心指标(LCP、FID、CLS),和真实用户体验挂钩。


七、总结一句话

优化 JS 性能,说难也不难,说简单也不简单。核心是:知道用户真正卡在哪、慢在哪,然后按需下手,不要一通胡乱优化。

你可能感兴趣的:(javascript,性能优化,开发语言)