繁星点点 Logo 繁星点点

前端性能优化技术

常用的前端性能优化技术汇总

约 13 分钟阅读 作者: 繁星点点
前端性能优化技术

🚀 前端性能优化技术合集与汇总(含示例)

1. 资源加载优化

异步加载(Asynchronous Loading)

描述:不阻塞页面渲染,后台加载资源,加载完成后执行。

应用场景:使用 asyncdefer 属性异步加载 JS 脚本。

示例

<!-- async: 下载完成后立即执行,可能中断HTML解析,执行顺序不定 -->
<script async src="analytics.js"></script>

<!-- defer: HTML解析完成后,DOMContentLoaded事件前按顺序执行 -->
<script defer src="main-bundle.js"></script>

懒加载(Lazy Loading)

描述:仅在需要时加载资源(如图片、视频、组件等)。

应用场景:图片、视频、组件、评论等在进入视口时再加载。

示例

<!-- 使用 loading="lazy" 属性(浏览器原生支持) -->
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" alt="Lazy loaded image">
<iframe src="placeholder.html" data-src="real-content.html" loading="lazy"></iframe>

<!-- 使用 Intersection Observer API 实现自定义懒加载 -->
<script>
  const images = document.querySelectorAll('img[data-src]');
  const observer = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        img.removeAttribute('data-src');
        observer.unobserve(img);
      }
    });
  });
  images.forEach(img => observer.observe(img));
</script>

预加载(Preload)

描述:页面加载时提前加载未来可能需要的资源。

应用场景:使用 <link rel="preload" href="..." as="..."> 提前加载重要资源(如字体、脚本)。

示例

<!-- 预加载关键CSS文件 -->
<link rel="preload" href="critical.css" as="style">

<!-- 预加载将在稍后使用的JS文件 -->
<link rel="preload" href="late-loaded-script.js" as="script">

<!-- 预加载字体文件 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

空闲时间调度(Idle Time Scheduling)

描述:使用 requestIdleCallback 在浏览器空闲时执行低优先级任务,提升页面响应。

应用场景:适用于非关键任务的加载和执行,如发送分析数据、预渲染不可见内容。

示例

requestIdleCallback((deadline) => {
  // 如果有剩余时间,或者任务必须执行
  if (deadline.timeRemaining() > 0 || deadline.didTimeout) {
    // 执行低优先级任务,例如发送统计数据
    sendAnalyticsData();
  }
});

2. 资源压缩与合并

图片压缩(Image Optimization)

描述:压缩图片文件,减少图片体积,使用高效格式(如 WebP、AVIF)。

应用场景:网站中所有使用的图片资源。

示例

  • 工具: 使用在线工具如 TinyPNG、Squoosh.app 或构建工具插件(如 imagemin-webpack-plugin)进行自动化压缩。
  • 格式选择: 使用 <picture> 标签提供多种格式供浏览器选择。
    <picture>
      <source srcset="image.avif" type="image/avif">
      <source srcset="image.webp" type="image/webp">
      <img src="image.jpg" alt="Optimized image">
    </picture>

JS / CSS 压缩(Minification)

描述:去除代码中的空格、注释等,减少文件体积,提升加载速度。

应用场景:生产环境中的所有 JavaScript 和 CSS 文件。

示例

  • 构建工具: 使用 Webpack、Rollup、Parcel 等构建工具,并配置相应的插件(如 TerserWebpackPlugin 用于 JS,CssMinimizerWebpackPlugin 用于 CSS)在生产构建时自动压缩代码。
    // webpack.config.js (示例)
    const TerserPlugin = require('terser-webpack-plugin');
    const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
    
    module.exports = {
      // ...
      optimization: {
        minimize: true,
        minimizer: [
          new TerserPlugin(),
          new CssMinimizerPlugin(),
        ],
      },
      // ...
    };

CSS 和 JS 合并(Concatenation)

描述:合并多个文件,减少 HTTP 请求次数(在 HTTP/1.1 环境下尤其重要,HTTP/2 中重要性降低但仍有益)。

应用场景:将多个小的 CSS 或 JS 文件合并成一个或少数几个较大的文件。

示例

  • 构建工具: Webpack、Rollup 等现代构建工具通常在打包过程中自动处理模块合并。确保配置合理,避免生成过大的单一文件,可以结合代码分割使用。
    // main.js (示例入口文件)
    import './moduleA.js';
    import './moduleB.js';
    import './styles.css';
    import './more-styles.css';
    
    // 构建工具会将这些导入合并到输出的 bundle 文件中

3. 代码优化

代码分割(Code Splitting)

描述:按需加载 JS 代码,减小首屏加载体积,常见于单页应用。

应用场景:使用动态 import() 和路由懒加载,按需加载模块。

示例

// 使用动态 import() 按需加载模块
document.getElementById('loadButton').addEventListener('click', () => {
  import('./heavy-module.js')
    .then(module => {
      module.doSomething();
    })
    .catch(err => {
      console.error('Failed to load module:', err);
    });
});

// 路由懒加载 (以 React Router 为例)
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const HomePage = lazy(() => import('./routes/HomePage'));
const AboutPage = lazy(() => import('./routes/AboutPage'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={HomePage} />
        <Route path="/about" component={AboutPage} />
      </Switch>
    </Suspense>
  </Router>
);

Tree Shaking

描述:删除未使用的代码(dead code elimination),减小打包体积,优化代码执行效率。

应用场景:适用于 Webpack、Rollup 等支持 Tree Shaking 的工具,需要使用 ES6 模块语法(import/export)。

示例

  • 构建工具配置: 在 Webpack 中,将 mode 设置为 production 会自动启用 Tree Shaking。确保你的代码使用 ES6 模块,并且没有副作用(side effects)阻止 Tree Shaking。
    // webpack.config.js
    module.exports = {
      mode: 'production', // 启用包括 Tree Shaking 在内的多项优化
      // ...
    };
  • 标记副作用: 如果某个模块有副作用(例如,全局样式注入、polyfill),可以在 package.json 中标记。
    // package.json
    {
      "name": "my-library",
      "version": "1.0.0",
      "sideEffects": false // 默认无副作用,利于 Tree Shaking
      // 或者指定有副作用的文件
      // "sideEffects": ["./src/polyfill.js", "*.css"]
    }

Font Subsetting

描述:只加载网页中使用的字符集,减少字体文件的体积。

应用场景:适用于自定义字体加载和优化,特别是中文字体等字符集庞大的字体。

示例

  • 工具: 使用 font-spider (国人开发,适合中文字体)、glyphhanger 或在线字体子集化工具,根据项目实际使用的文字生成精简后的字体文件。
    # 使用 font-spider (示例)
    # 1. 在 CSS 中正常引用字体
    # @font-face {
    #   font-family: 'MyFont';
    #   src: url('../fonts/MyFont.ttf');
    # }
    # body {
    #   font-family: 'MyFont';
    # }
    # 2. 运行命令扫描 HTML 文件并生成子集字体
    font-spider ./index.html

4. 浏览器渲染优化

减少重排与重绘(Reflow & Repaint)

描述:避免频繁修改布局(如 widthheighttopleft),减少重排(Reflow/Layout)。优化 DOM 操作,避免页面卡顿和性能瓶颈。

应用场景:涉及 DOM 元素尺寸、位置、内容变化的 JavaScript 操作。

示例

// 不好的做法:每次循环都读取 offsetHeight,可能触发重排
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = elements[i].offsetHeight + 'px'; // 读取触发重排
}

// 改进:先读后写,分离读写操作
const heights = [];
for (let i = 0; i < elements.length; i++) {
  heights[i] = elements[i].offsetHeight; // 批量读取
}
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = heights[i] + 'px'; // 批量写入
}

// 使用 CSS transform 代替 top/left 实现位移动画,避免重排
// 不推荐
// element.style.left = x + 'px';
// element.style.top = y + 'px';

// 推荐
element.style.transform = `translate(${x}px, ${y}px)`;

合并 DOM 操作

描述:将多次 DOM 操作合并成一次,减少重排和重绘的频率。

应用场景:需要向 DOM 中插入多个元素时。

示例

const list = document.getElementById('myList');
const items = ['Apple', 'Banana', 'Cherry'];

// 不好的做法:每次循环都操作 DOM
// items.forEach(itemText => {
//   const li = document.createElement('li');
//   li.textContent = itemText;
//   list.appendChild(li); // 每次 appendChild 都可能触发重排/重绘
// });

// 推荐:使用 DocumentFragment
const fragment = document.createDocumentFragment();
items.forEach(itemText => {
  const li = document.createElement('li');
  li.textContent = itemText;
  fragment.appendChild(li); // 操作 Fragment 不会触发重排/重绘
});
list.appendChild(fragment); // 最后一次性插入 DOM

硬件加速(Transform & Opacity)

描述:使用 transformopacity 代替布局属性(如 topleft)进行动画,利用 GPU 加速,提高动画流畅度。

应用场景:实现位移、缩放、旋转、透明度等动画效果。

示例

.animated-element {
  /* 不推荐:使用 top/left 可能导致重排,动画可能卡顿 */
  /* position: absolute; */
  /* left: 0; */
  /* transition: left 0.3s ease; */

  /* 推荐:使用 transform 利用 GPU 加速 */
  transform: translateX(0);
  transition: transform 0.3s ease, opacity 0.3s ease;
  opacity: 1;
}

.animated-element.move {
  /* transform: translateX(100px); */
  transform: translate3d(100px, 0, 0); /* 强制开启 GPU 加速 */
}

.animated-element.fade-out {
  opacity: 0;
}

will-change 提前通知浏览器

描述:使用 will-change 提前告知浏览器即将发生的样式变化,让浏览器可以提前进行优化准备,避免渲染瓶颈。

应用场景:用于即将发生复杂变化的元素,特别是动画元素。

示例

.element-about-to-animate {
  /* 告知浏览器 transform 和 opacity 属性即将发生变化 */
  will-change: transform, opacity;
}

.element-about-to-animate:hover {
  transform: scale(1.1);
  opacity: 0.8;
}

/* 注意:不要滥用 will-change,仅在确实需要时添加,并在变化结束后移除 */
/* 可以通过 JavaScript 在动画开始前添加,结束后移除 */

5. 网络优化

HTTP/2 / HTTP/3 协议

描述:启用 HTTP/2 或 HTTP/3,利用其多路复用、头部压缩、服务器推送(HTTP/2)、基于 UDP 的 QUIC(HTTP/3)等特性,提高并行传输效率,减少请求延迟。

应用场景:Web 服务器配置。

示例

  • 服务器配置: 在 Nginx, Apache 或其他 Web 服务器上启用 HTTP/2 或 HTTP/3 支持(通常需要 HTTPS)。
    # Nginx 配置启用 HTTP/2 (示例)
    server {
        listen 443 ssl http2;
        # ... 其他 SSL 配置 ...
    }
    # Nginx 配置启用 HTTP/3 (需要较新版本和额外模块)
    server {
        listen 443 quic reuseport;
        listen 443 ssl http2;
        # ... 其他 SSL 和 HTTP/3 配置 ...
        add_header Alt-Svc 'h3=":443"; ma=86400'; # 告知浏览器支持 HTTP/3
    }
  • 验证: 使用浏览器开发者工具的网络(Network)面板查看协议版本。

Content Delivery Network (CDN)

描述:使用 CDN 将静态资源(如图片、CSS、JS)缓存到全球各地的边缘节点,用户从最近的节点获取资源,减少网络延迟,提高加载速度。

应用场景:网站的静态资源分发。

示例

  • 服务商: 选择 CDN 服务商(如 Cloudflare, Akamai, AWS CloudFront, 阿里云 CDN, 腾讯云 CDN 等)。
  • 配置: 将静态资源的 URL 指向 CDN 提供的域名。
    <!-- 原始路径 -->
    <!-- <script src="/assets/app.js"></script> -->
    <!-- <img src="/images/logo.png"> -->
    
    <!-- 使用 CDN 路径 -->
    <script src="https://cdn.example.com/assets/app.js"></script>
    <img src="https://cdn.example.com/images/logo.png">

缓存优化(Caching)

描述:使用 HTTP 缓存头(如 Cache-ControlETagLast-Modified)优化资源缓存,让浏览器可以复用已下载的资源,减少重复请求。

应用场景:所有可以通过 HTTP 请求的资源。

示例

  • 服务器配置: 配置 Web 服务器发送合适的缓存头。
    # Nginx 配置示例
    location ~* \.(?:css|js)$ {
        # 强缓存:缓存 1 年
        add_header Cache-Control "public, max-age=31536000, immutable";
        access_log off;
    }
    
    location ~* \.(?:jpg|jpeg|png|gif|ico|webp|avif)$ {
        # 强缓存:缓存 1 个月
        add_header Cache-Control "public, max-age=2592000";
        access_log off;
    }
    
    location = /index.html {
        # 协商缓存:每次验证
        add_header Cache-Control "no-cache";
    }
  • ETag: 服务器为资源生成唯一标识,浏览器下次请求时通过 If-None-Match 发送此标识,服务器比对后若无变化则返回 304 Not Modified。
  • Last-Modified: 服务器告知资源最后修改时间,浏览器下次通过 If-Modified-Since 发送此时间,服务器比对后若无更新则返回 304。

DNS 预解析 / 预连接(DNS Prefetch / Preconnect)

描述:提前解析域名或建立到目标源(域名+端口)的 TCP 连接(甚至 TLS 握手),减少后续资源请求时的延迟。

应用场景:页面中引用了来自其他域名的资源(如 CDN、API、第三方脚本)。

示例

<!-- DNS 预解析:提前解析域名 -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//api.example.com">

<!-- 预连接:提前完成 DNS 解析 + TCP 握手 + TLS 协商 -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://api.example.com">

6. 服务端优化

Server-Side Rendering (SSR) / Static Site Generation (SSG)

描述:使用 SSR(服务器端渲染)或 SSG(静态站点生成)在服务器端生成完整的 HTML 页面或预渲染的静态 HTML 文件,直接发送给浏览器,加快首屏显示速度,并有利于 SEO。

应用场景:内容型网站、需要良好 SEO 的应用、追求极致首屏性能的场景。

示例

  • 框架: 使用支持 SSR/SSG 的前端框架,如 Next.js (React), Nuxt.js (Vue), SvelteKit (Svelte), Gatsby (React, SSG), Astro 等。
    // Next.js 页面示例 (SSR)
    export async function getServerSideProps(context) {
      const res = await fetch(`https://api.example.com/data`);
      const data = await res.json();
      return { props: { data } }; // 数据将作为 props 传递给页面组件
    }
    function Page({ data }) {
      // 渲染页面...
    }
    export default Page;
    
    // Next.js 页面示例 (SSG)
    export async function getStaticProps() {
      const res = await fetch(`https://api.example.com/data`);
      const data = await res.json();
      return { props: { data } };
    }
    // ... Page 组件同上 ...

API 优化

描述:缩短 API 接口的响应时间,使用合适的数据格式(如 JSON),采用有效的缓存策略(如 Redis 缓存查询结果),减少 API 请求处理的延迟。

应用场景:所有前后端数据交互的接口。

示例

  • 后端: 优化数据库查询(添加索引、慢查询分析)、使用缓存层(Redis, Memcached)、优化业务逻辑、使用异步处理非核心任务。
  • 数据格式: 确保 API 返回精简的 JSON 数据,避免冗余字段。
  • 网络: 减少 API 服务器与客户端之间的网络延迟(如使用 CDN 加速 API)。

GraphQL 优化

描述:采用 GraphQL 代替传统 RESTful API,允许客户端精确指定需要获取的数据结构,避免了 REST 中常见的 over-fetching(获取过多数据)和 under-fetching(需要多次请求才能获取足够数据)的问题。

应用场景:复杂数据关联、移动端应用、需要灵活数据查询的场景。

示例

# GraphQL 查询示例:只请求需要的字段
query GetUserProfile {
  user(id: "123") {
    id
    name
    email
    posts(last: 5) { # 只获取最近 5 篇文章
      title
      createdAt
    }
  }
}

# 相比之下,REST 可能需要多个请求或返回大量不需要的数据
# GET /users/123
# GET /users/123/posts?limit=5

7. 前端缓存与持久化

LocalStorage / SessionStorage

描述:使用 Web Storage API (LocalStorage, SessionStorage) 在浏览器端存储少量键值对数据。LocalStorage 数据持久存在,除非手动清除;SessionStorage 数据在会话结束后(通常是浏览器标签页关闭时)清除。

应用场景:存储用户偏好设置、少量非敏感数据、临时状态等,减少不必要的服务器请求。

示例

// LocalStorage: 持久存储
localStorage.setItem('theme', 'dark');
const currentTheme = localStorage.getItem('theme');
localStorage.removeItem('theme');
localStorage.clear(); // 清除所有

// SessionStorage: 会话级存储
sessionStorage.setItem('sessionId', 'abc123xyz');
const sessionId = sessionStorage.getItem('sessionId');

注意: Web Storage 容量有限(通常 5-10MB),且是同步操作,可能阻塞主线程,不适合存储大量数据。

IndexedDB

描述:一个在浏览器中存储大量结构化数据(包括文件/Blob)的底层 API。它创建了一个事务型数据库,支持索引,可以进行高性能搜索。

应用场景:离线数据存储、大型数据集缓存、PWA(Progressive Web Apps)的数据存储。

示例

// 打开(或创建)数据库
const request = indexedDB.open('myDatabase', 1);

request.onupgradeneeded = event => {
  const db = event.target.result;
  // 创建一个对象存储空间(类似表)
  const objectStore = db.createObjectStore('customers', { keyPath: 'id' });
  // 创建索引
  objectStore.createIndex('name', 'name', { unique: false });
};

request.onsuccess = event => {
  const db = event.target.result;
  // 使用事务进行数据操作
  const transaction = db.transaction(['customers'], 'readwrite');
  const objectStore = transaction.objectStore('customers');

  // 添加数据
  const addRequest = objectStore.add({ id: '1', name: 'Alice', email: '[email protected]' });
  addRequest.onsuccess = () => console.log('Data added');

  // 获取数据
  const getRequest = objectStore.get('1');
  getRequest.onsuccess = () => console.log('Retrieved data:', getRequest.result);
};

request.onerror = event => {
  console.error('Database error:', event.target.errorCode);
};

注意: IndexedDB API 较为复杂,通常建议使用封装库(如 Dexie.js, idb)。

缓存 API (Cache API)

描述:通常与 Service Worker 结合使用,提供一个用于存储和检索网络请求及其相应响应的系统。它允许你缓存资源,以便在离线时或为了提高性能而使用。

应用场景:PWA 的离线缓存、静态资源缓存、API 响应缓存。

示例

// 在 Service Worker 中使用 Cache API
const CACHE_NAME = 'my-site-cache-v1';
const urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        console.log('Opened cache');
        return cache.addAll(urlsToCache); // 将资源添加到缓存
      })
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request) // 尝试从缓存中查找匹配的请求
      .then(response => {
        // 如果缓存中有匹配的响应,则返回它
        if (response) {
          return response;
        }
        // 否则,从网络获取
        return fetch(event.request);
      })
  );
});

8. 响应式设计与适配

媒体查询(Media Queries)

描述:使用 CSS 媒体查询根据设备的特性(如视口宽度、高度、方向、分辨率等)应用不同的样式规则。

应用场景:实现响应式布局,为不同屏幕尺寸提供优化体验,加载适合的资源。

示例

/* 默认样式 (移动优先) */
.container {
  width: 95%;
  margin: 0 auto;
}

/* 平板设备 */
@media (min-width: 768px) {
  .container {
    width: 90%;
  }
  .sidebar {
    display: block; /* 在平板上显示侧边栏 */
  }
}

/* 桌面设备 */
@media (min-width: 1024px) {
  .container {
    width: 80%;
    max-width: 1200px;
  }
}

/* 加载不同背景图 */
body {
  background-image: url('background-small.jpg');
}
@media (min-width: 768px) {
  body {
    background-image: url('background-large.jpg');
  }
}

图片适配(Responsive Images)

描述:使用 HTML 的 <picture> 元素或 <img> 元素的 srcsetsizes 属性,让浏览器根据设备特性(如屏幕尺寸、分辨率、网络状况)选择加载最合适的图片资源。

应用场景:为不同设备提供不同尺寸或格式的图片,节省带宽,提高加载速度。

示例

<!-- 使用 srcset 和 sizes -->
<img srcset="image-320w.jpg 320w,
             image-480w.jpg 480w,
             image-800w.jpg 800w"
     sizes="(max-width: 320px) 280px,
            (max-width: 480px) 440px,
            800px"
     src="image-800w.jpg" alt="Responsive image">

<!-- 使用 <picture> 元素提供不同格式或裁剪 -->
<picture>
  <source media="(min-width: 650px)" srcset="image-large.webp" type="image/webp">
  <source media="(min-width: 465px)" srcset="image-medium.webp" type="image/webp">
  <source srcset="image-small.webp" type="image/webp">
  <!-- Fallback for browsers that don't support WebP or <picture> -->
  <source media="(min-width: 650px)" srcset="image-large.jpg">
  <source media="(min-width: 465px)" srcset="image-medium.jpg">
  <img src="image-small.jpg" alt="Art directed image">
</picture>

9. 智能化和延迟加载

智能加载(Smart Loading)

描述:根据用户的行为、网络状况(如使用 Network Information API)、设备性能或数据节省偏好,智能地决定加载哪些内容或何种质量的内容。

应用场景:在慢速网络下加载低质量图片或延迟加载非关键脚本,根据用户交互历史预测并预加载可能访问的页面。

示例

// 检查网络状况决定加载资源质量 (概念示例)
if ('connection' in navigator) {
  const connection = navigator.connection;
  if (connection.saveData === true) {
    // 用户启用了数据节省模式,加载低质量资源
    loadLowQualityResources();
  } else if (connection.effectiveType === '4g') {
    // 网络良好,加载高质量资源
    loadHighQualityResources();
  } else {
    // 网络一般或未知,加载标准资源
    loadStandardResources();
  }
} else {
  // 不支持 Network Information API,加载标准资源
  loadStandardResources();
}

按需加载(On-demand Loading)

描述:只有在用户明确需要时(如点击按钮、滚动到特定区域)才加载更多内容或功能。

应用场景:无限滚动列表、点击“加载更多”按钮、加载大型库或组件。

示例

// 点击按钮加载更多评论
const loadMoreButton = document.getElementById('loadMoreComments');
let currentPage = 1;

loadMoreButton.addEventListener('click', () => {
  currentPage++;
  fetch(`/api/comments?page=${currentPage}`)
    .then(response => response.json())
    .then(comments => {
      renderComments(comments);
      if (comments.length === 0) {
        loadMoreButton.style.display = 'none'; // 没有更多评论了
      }
    });
});

// 结合 Intersection Observer 实现无限滚动加载
const sentinel = document.getElementById('infinite-scroll-sentinel');
const observer = new IntersectionObserver(entries => {
  if (entries[0].isIntersecting) {
    loadMoreContent();
  }
});
observer.observe(sentinel);

10. 字体优化

字体懒加载

描述:仅在需要显示相应文本时才开始加载字体文件,或者使用 font-display CSS 属性控制字体加载过程中的文本显示行为,避免字体加载阻塞页面渲染或导致文本闪烁(FOIT/FOUT)。

应用场景:优化使用了自定义 Web 字体的页面加载体验。

示例

@font-face {
  font-family: 'MyCustomFont';
  src: url('myfont.woff2') format('woff2');
  /* font-display 控制字体加载行为 */
  /* swap: 先显示后备字体,字体加载完后替换。可能导致 FOUT (Flash of Unstyled Text) */
  font-display: swap;

  /* block: 短暂阻塞(约3秒),期间不显示文本。若超时则显示后备字体,加载完后替换。*/
  /* font-display: block; */

  /* fallback: 极短阻塞(约100ms),期间不显示文本。若超时则显示后备字体,后续不再替换。*/
  /* font-display: fallback; */

  /* optional: 极短阻塞(约100ms),期间不显示文本。若超时则显示后备字体,字体仍在后台下载,但仅在下次导航时使用。*/
  /* font-display: optional; */
}

body {
  font-family: 'MyCustomFont', sans-serif; /* 指定后备字体 */
}
  • JS 懒加载: 可以使用 JavaScript 监听特定元素进入视口或其他条件,然后动态添加包含 @font-face 规则的 CSS 或直接加载字体。

合并字体文件

描述:如果页面使用了同一字体家族的多个字重(如 Regular, Bold, Italic),并且这些字重分别在不同的字体文件中,可以将它们合并成一个或少数几个文件(如果格式支持,如 WOFF2 可变字体),或者只加载必要的字重和字符集,以减少 HTTP 请求次数。

应用场景:使用了多个字体文件的网站。

示例

  • 策略: 评估是否所有字重都是必需的。如果可能,使用可变字体(Variable Fonts),一个文件包含所有变体。
  • 工具: 使用字体编辑工具(如 FontForge)或专门的服务来合并字体文件或创建子集。
  • CSS: 确保 @font-face 规则正确指向合并后的文件或只引用需要的字重。

11. WebP 和 AVIF 格式

WebP / AVIF 图片格式

描述:使用现代图片格式 WebP 或 AVIF 替代传统的 JPG、PNG、GIF。它们通常能在相同视觉质量下提供更小的文件体积(WebP 比 JPG 小约 25-35%,AVIF 比 JPG 小约 50%),支持有损和无损压缩、透明度以及动画。

应用场景:网站上所有使用的图片。

示例

<!-- 使用 <picture> 元素提供多种格式 -->
<picture>
  <!-- 优先尝试 AVIF -->
  <source srcset="image.avif" type="image/avif">
  <!-- 其次尝试 WebP -->
  <source srcset="image.webp" type="image/webp">
  <!-- 最后回退到 JPG -->
  <img src="image.jpg" alt="Modern image format example">
</picture>

<!-- 在 CSS 中使用 -->
<style>
.hero {
  /* 浏览器会自动选择它支持的第一个格式 */
  background-image: url('hero.avif'), url('hero.webp'), url('hero.jpg');
}
</style>
  • 转换工具: 使用 Squoosh.app、cwebp/avifenc 命令行工具或构建插件(如 imagemin-webp)将现有图片转换为 WebP/AVIF。

12. JavaScript 性能优化

减少 JavaScript 阻塞

描述:避免长时间运行的 JavaScript 任务阻塞主线程,导致页面无响应。通过异步加载(async/defer)、延迟执行非关键脚本、将耗时任务移至 Web Worker 或使用 requestIdleCallback 来实现。

应用场景:优化页面加载和交互过程中的 JavaScript 执行。

示例

  • async/defer: 见 1.1 异步加载示例。
  • 延迟执行: 对于非首屏必需的 JS(如聊天插件、广告脚本),可以在页面主要内容加载完成后再加载和执行。
    window.addEventListener('load', () => {
      const script = document.createElement('script');
      script.src = 'non-critical-script.js';
      document.body.appendChild(script);
    });
  • Web Worker: 见 12.3 Web Worker 示例。
  • requestIdleCallback: 见 1.4 空闲时间调度示例。

优化垃圾回收(GC)

描述:编写更有效的代码以减少内存泄漏和不必要的内存分配,从而降低垃圾回收(GC)的频率和持续时间,避免 GC 暂停导致页面卡顿。

应用场景:长时间运行的应用、包含大量动态数据和事件监听器的页面。

示例

  • 避免全局变量: 全局变量不易被回收。
  • 及时移除事件监听器: 在元素移除或组件卸载时,使用 removeEventListener 清除不再需要的监听器。
    function handleClick() { /* ... */ }
    element.addEventListener('click', handleClick);
    // ... 当不再需要时 ...
    element.removeEventListener('click', handleClick);
  • 管理闭包: 注意闭包可能意外地持有不再需要的变量引用。
  • 使用 WeakMap / WeakSet: 对于对象键或集合成员,如果希望它们在没有其他引用的情况下能被 GC 回收,可以使用 WeakMap 或 WeakSet。
    const metadata = new WeakMap(); // 使用 WeakMap 存储对象元数据
    let obj = {};
    metadata.set(obj, { info: 'some data' });
    // 当 obj 不再被引用时,WeakMap 中的条目也会被自动移除,不会阻止 obj 被回收
    obj = null; // 假设这是 obj 的最后一个引用

Web Worker

描述:将计算密集型或长时间运行的 JavaScript 任务放到后台线程(Web Worker)中执行,避免阻塞主线程,保持 UI 的响应性。

应用场景:复杂计算、数据处理、图像处理、后台数据同步等。

示例

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

// 向 Worker 发送数据
worker.postMessage({ data: [1, 2, 3, 4, 5] });

// 接收来自 Worker 的消息
worker.onmessage = event => {
  console.log('Result from worker:', event.data.result);
};

// 处理错误
worker.onerror = error => {
  console.error('Worker error:', error);
};

// worker.js (后台线程)
self.onmessage = event => {
  console.log('Data received in worker:', event.data.data);
  // 执行耗时计算
  const result = event.data.data.reduce((sum, val) => sum + val * val, 0);

  // 将结果发送回主线程
  self.postMessage({ result: result });
};

13. Web API 性能优化

Intersection Observer API

描述:提供一种异步观察目标元素与其祖先元素或顶级文档视口交叉状态变化的方法。比传统的滚动事件监听更高效。

应用场景:图片/组件懒加载、无限滚动、广告可见性跟踪、触发动画等。

示例

const targetElement = document.getElementById('observeMe');

const callback = (entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // 元素进入视口
      console.log('Element is visible!');
      targetElement.classList.add('visible');
      // 如果只需要触发一次,可以停止观察
      // observer.unobserve(targetElement);
    } else {
      // 元素离开视口
      console.log('Element is hidden!');
      targetElement.classList.remove('visible');
    }
  });
};

const options = {
  root: null, // 相对于视口
  rootMargin: '0px',
  threshold: 0.5 // 元素 50% 可见时触发回调
};

const observer = new IntersectionObserver(callback, options);
observer.observe(targetElement);

Performance API

描述:提供访问浏览器性能相关信息的接口,可以用来精确测量代码执行时间、资源加载时间、页面导航性能等。

应用场景:性能监控、代码性能分析、资源加载优化分析。

示例

// 测量代码块执行时间
performance.mark('start-calculation');
// ... 执行一些耗时操作 ...
calculateFibonacci(40);
performance.mark('end-calculation');
performance.measure('calculation-duration', 'start-calculation', 'end-calculation');

// 获取所有测量结果
const measures = performance.getEntriesByType('measure');
measures.forEach(measure => {
  console.log(`${measure.name}: ${measure.duration}ms`);
});

// 获取资源加载性能数据
const resources = performance.getEntriesByType('resource');
resources.forEach(resource => {
  console.log(`Resource ${resource.name} loaded in ${resource.duration}ms`);
});

// 获取导航性能数据
const navigation = performance.getEntriesByType('navigation')[0];
console.log(`DOM content loaded in ${navigation.domContentLoadedEventEnd}ms`);
console.log(`Page loaded in ${navigation.loadEventEnd}ms`);

// 清除标记和测量
performance.clearMarks();
performance.clearMeasures();

Service Worker

描述:一个运行在浏览器后台的脚本,独立于网页,可以拦截和处理网络请求、推送通知、后台同步等。是构建 PWA 的核心技术之一。

应用场景:实现离线缓存、资源拦截与代理、推送通知、后台数据同步。

示例

// main.js (注册 Service Worker)
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js')
      .then(registration => {
        console.log('ServiceWorker registration successful with scope: ', registration.scope);
      })
      .catch(error => {
        console.log('ServiceWorker registration failed: ', error);
      });
  });
}

// sw.js (Service Worker 脚本 - 缓存优先策略示例)
const CACHE_NAME = 'my-app-cache-v1';

self.addEventListener('install', event => {
  // 安装时缓存核心资源
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll([
        '/',
        '/index.html',
        '/styles.css',
        '/app.js'
      ]);
    })
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // 缓存命中,返回缓存的响应
        if (response) {
          return response;
        }
        // 缓存未命中,从网络获取
        return fetch(event.request).then(networkResponse => {
          // 可选:将新的响应添加到缓存
          // caches.open(CACHE_NAME).then(cache => cache.put(event.request, networkResponse.clone()));
          return networkResponse;
        });
      })
      .catch(() => {
        // 网络和缓存都失败时,可以返回一个备用的离线页面
        // return caches.match('/offline.html');
      })
  );
});

标签