FORMA

性能优化

防抖/节流、渲染与内存、资源预加载及引擎行为是前端性能的主要抓手。相关 CSS 见 视觉效果Head 资源提示

一、代码层面优化

1. 防抖(Debounce)与节流(Throttle)实现

防抖 – 延迟执行,连续触发只执行最后一次

应用场景:搜索输入、窗口resize、表单验证。

js
function debounce(fn, delay) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
// 立即执行版(首次立即执行,后续防抖)
function debounceImmediate(fn, delay) {
  let timer = null;
  return function (...args) {
    if (timer === null) fn.apply(this, args);
    clearTimeout(timer);
    timer = setTimeout(() => {
      timer = null;
    }, delay);
  };
}

节流 – 固定频率执行

应用场景:滚动事件、鼠标移动、射击游戏。

js
// 时间戳版(立即执行,停止后不执行最后一次)
function throttle(fn, interval) {
  let last = 0;
  return function (...args) {
    const now = Date.now();
    if (now - last >= interval) {
      fn.apply(this, args);
      last = now;
    }
  };
}
// 定时器版(延时执行,停止后会再执行一次)
function throttleTimer(fn, interval) {
  let timer = null;
  return function (...args) {
    if (timer) return;
    timer = setTimeout(() => {
      fn.apply(this, args);
      timer = null;
    }, interval);
  };
}
// 融合版:保证结束时还执行一次(常用)
function throttleMerge(fn, interval) {
  let last = 0,
    timer = null;
  return function (...args) {
    const now = Date.now();
    const remaining = interval - (now - last);
    if (remaining <= 0) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      fn.apply(this, args);
      last = now;
    } else if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        last = Date.now();
        timer = null;
      }, remaining);
    }
  };
}

2. 避免内存泄漏

常见泄漏源示例解决方案
意外的全局变量function foo() { a = 1; }使用 'use strict',始终声明变量 let/const/var
未清理的定时器setInterval(() => {...}, 1000) 未清除在组件卸载或不需要时 clearInterval/clearTimeout
未移除的事件监听window.addEventListener('resize', handler)提供对应的 removeEventListener,尤其在 SPA 路由切换时
闭包引用过大闭包持有大数组或 DOM 元素将不需要的变量设为 null,避免长期持有
DOM 引用未清在 JS 中保存已删除 DOM 节点的引用删除节点前先置引用为 null
脱离 DOM 树但被 JS 引用let elem = document.getElementById('btn'); elem.remove(); 但变量仍存在主动 elem = null
Map/Set 未清除使用普通 Map 存储 DOM 节点,节点删除后 Map 仍持有使用 WeakMap / WeakSet 自动弱引用

3. 重排(Reflow)与重绘(Repaint)的触发与优化

  • 重排(reflow):布局几何属性变化(宽高、边距、位置、字体、添加/删除 DOM、窗口 resize、获取 offsetWidth 等)→ 重新计算布局树。
  • 重绘(repaint):视觉样式变化(背景色、颜色、可见性)但不影响布局 → 仅重新绘制。

优化原则

  • 避免逐条修改样式,使用 classcssText
  • 使元素脱离文档流(position: absolute/fixeddisplay: none)进行批量 DOM 操作。
  • 避免频繁读取布局属性(如 offsetTop),改用变量缓存。
  • 复杂动画使用 transformopacity(只触发合成,不触发重排/重绘)。
  • 使用 documentFragment 批量更新 DOM。
  • 将动画效果应用到 position: fixed/absolute 元素上。
js
// 坏
for (let i = 0; i < 1000; i++) {
  element.style.left = i + "px"; // 每次触发重排
}
// 好 - 使用 transform
element.style.transform = `translateX(1000px)`;

4. 函数节流/防抖 + requestAnimationFrame 动画最佳实践

  • 滚动事件:用 requestAnimationFrame 合并样式修改,避免过度触发。
js
let ticking = false;
window.addEventListener("scroll", () => {
  if (!ticking) {
    requestAnimationFrame(() => {
      // 更新样式或处理
      ticking = false;
    });
    ticking = true;
  }
});
  • 动画循环:始终使用 requestAnimationFrame 而非 setInterval,它匹配屏幕刷新率,且在页面不可见时停止,节省资源。
  • 配合节流:限制高频率事件(如 mousemove)的回调中执行复杂操作,但动画更新本身仍用 rAF。
js
let lastTimestamp = 0;
function onMouseMove(e) {
  throttle(() => {
    requestAnimationFrame(() => updatePosition(e.clientX, e.clientY));
  }, 16);
}

二、网络优化

1. 资源预加载指令

指令作用典型场景
<link rel="preload" href="..." as="...">提前加载当前页面必需资源(高优先级)字体、关键 CSS/JS、大图
<link rel="prefetch" href="...">预取下一页资源(低优先级,空闲时加载)路由懒加载的下一屏模块
<link rel="preconnect" href="https://...">提前建立 TCP+TLS 连接跨域 API 域名、CDN
<link rel="dns-prefetch" href="...">提前做 DNS 解析第三方资源域名

注意:preload 要配合 as 属性正确设置,否则可能二次加载。

2. HTTP/2 多路复用与压缩

  • HTTP/2 多路复用:单一连接上并发传输多个请求和响应,消除队头阻塞(应用层)。提升复用 TCP 连接效率,配合服务器推送可主动发送资源。
  • 压缩算法
    • Gzip:广泛支持,压缩比中等。
    • Brotli:压缩比更高,尤其文本资源(JS/CSS/HTML),比 gzip 小 20%~30%。现代浏览器和服务器(Nginx、CDN)均支持。
    • 启用方法:Content-Encoding: brgzip

3. 图片懒加载与响应式图片

懒加载

  • 使用 IntersectionObserver 监听图片进入视口,替换 src
  • 原生懒加载:<img loading="lazy">(兼容性好,但精细控制可用 JS)。
js
const imgObserver = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      imgObserver.unobserve(img);
    }
  });
});
document.querySelectorAll("img[data-src]").forEach((img) => imgObserver.observe(img));

响应式图片

  • srcset + sizes:根据屏幕宽度选择不同尺寸。
html
<img
  srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
  sizes="(max-width: 600px) 480px, (max-width: 1000px) 800px, 1200px"
  src="fallback.jpg"
  alt="..."
/>
  • <picture> 元素:根据媒体查询或格式支持选择(如 WebP/AVIF)。
html
<picture>
  <source srcset="image.avif" type="image/avif" />
  <source srcset="image.webp" type="image/webp" />
  <img src="image.jpg" alt="" />
</picture>

三、运行时优化:V8 引擎原理

V8 是 Chrome 和 Node.js 中的 JavaScript 引擎,理解其内部机制有助于写出高性能代码。

1. 隐式类型转换

在需要类型时,JS 会自动转换,但会降低性能并可能导致意外。

js
// 坏:触发类型转换,且可能无法优化
const total = 100 + "100"; // "100100"
// 好:保持类型一致
const total = 100 + Number("100");

优化建议:

  • 使用 === 而非 ==(避免类型猜测)。
  • 避免在热函数中改变变量类型(会改变隐藏类)。

2. 隐藏类(Hidden Classes)

V8 将对象结构(属性名、顺序)编码为隐藏类。如果两个对象具有相同的属性名和顺序,它们共享同一个隐藏类,从而加速属性访问。

导致隐藏类退化的操作:

  • 动态添加/删除属性。
  • 属性赋值顺序不同。
js
// 相同隐藏类
const p1 = { name: "A", age: 20 };
const p2 = { name: "B", age: 22 };

// 不同隐藏类(差顺序)
const p3 = { age: 20, name: "C" };

优化:在构造函数或对象字面量中一次性定义所有属性,不要动态添加。

3. 去优化(Deoptimization)

V8 在运行时进行推测优化(假设代码模式不变)。当假设被打破时,会回退到非优化代码,称为去优化。

常见触发去优化的操作:

  • 函数中传入不同类型参数(例如有时是 number,有时是 string)。
  • try/catch 中的热代码路径。
  • 使用 debuggerwitheval

优化建议:

  • 保持函数参数类型稳定。
  • 避免在高频率函数(如动画循环)中使用 try/catch,可以将错误处理放到外层。
  • 切勿在生产代码中使用 evalwith

总结

类别关键技术点
防抖节流防抖合并连续触发,节流限制频率;实现中注意 this 和参数传递、立即执行/尾部执行变体
内存泄漏警惕全局变量、定时器、事件监听、闭包、DOM 引用;使用 WeakMap/WeakSet 辅助
渲染优化减少重排重绘,利用 transform/opacity,批量 DOM 操作,requestAnimationFrame 合并更新
网络预加载preload(关键)、prefetch(下页)、preconnect(连接预热)、dns-prefetch
HTTP 优化HTTP/2 多路复用 + Brotli/gzip 压缩
图片优化懒加载(IntersectionObserver / loading="lazy")、srcset + sizes、picture + WebP/AVIF
V8 优化保持类型稳定(避免隐式转换)、使用一致的隐藏类(完整定义对象属性)、避免去优化(稳定参数)

结合 Lighthouse、Performance 面板与内存快照做针对性优化。

参考文献

以下链接在编写时均可正常访问:

资料说明
MDN:requestAnimationFrame动画帧
MDN:rel=preload预加载
web.dev:Performance性能专题(英文)
web.dev:Optimize LCP加载性能(英文)