前端性能优化实践方向与方法

0x01 代码优化与压缩

(1)HTML

移除不必要的空白字符、注释和冗余标签,以减少文件大小

  1. 使用命令 npm install html-minifier -g 安装 HTML Minifier

  2. 使用命令 html-minifier -V 确认安装成功

  3. 在 Node.js 环境中配置 index.js

    // 引入 HTML Minifier
    const minify = require("html-minifier").minify;
    
    // 处理 HTML 文本
    let result = minify('<p title="blah" id="moo">foo</p>', {
      removeAttributeQuotes: true,
    });
    
    // 输出处理结果
    console.log(result);
    
  4. 使用命令 node .\index.js 运行,输出结果为:<p title=blah id=moo>foo</p>

  5. 详细参考:https://www.npmjs.com/package/html-minifier
    在线使用:https://kangax.github.io/html-minifier/

(2)CSS

精简样式表,避免使用冗余或过时的属性,合理组织选择器以减少计算复杂度

  1. 使用命令 npm install cssnano postcss postcss-cli --save-dev 安装 PostCSS 与 CSSNaNo

  2. 在 Node.js 环境中配置 postcss.config.js

    module.exports = {
      plugins: [
        require("cssnano")({
          preset: "default",
        }),
      ],
    };
    
  3. 使用命令 npx postcss input.css > output.css 运行,生成优化后的结果 output.css

  4. 详细参考:https://www.cssnano.cn/docs/introduction/

  5. 其他工具:

    1. PostCSS:https://www.postcss.com.cn/
    2. PurgeCSS:https://www.purgecss.cn/

(3)JavaScript

使用工具(如 Webpack 等)或在线服务对代码进行压缩,移除空格、注释和不必要的字符

  1. 使用命令 npm install terser-webpack-plugin --save-dev 安装 terser-webpack-plugin

  2. 在 Node.js 环境中配置 webpack.config.js

    const TerserPlugin = require("terser-webpack-plugin");
    
    module.exports = {
      optimization: {
        minimize: true,
        minimizer: [new TerserPlugin()],
      },
    };
    
  3. 使用命令 npx webpack 运行 Webpack 打包工具,并优化 JavaScript 代码

(4)合并文件

将多个 CSS、JavaScript 文件合并为一个,减少 HTTP 请求的数量

  • 在 Webpack 中,可以通过配置 entryoutput 选项来自动合并多个模块到一个文件中
  • 在 Gulp 中,可以使用 gulp-concat 插件来合并文件

0x02 静态资源优化

(1)压缩图片

(2)图片懒加载

a. 原生 JavaScript

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <style>
    img {
      width: 1000px;
      height: 700px;
      background-color: wheat;
      object-fit: cover;
      object-position: center;
    }
  </style>
</head>

<body>
  <img src="" data-src="./images/1.jpg" alt="" />
  <img src="" data-src="./images/2.jpg" alt="" />
  <img src="" data-src="./images/3.jpg" alt="" />
  <img src="" data-src="./images/4.jpg" alt="" />
  <img src="" data-src="./images/5.jpg" alt="" />
  <script>
    /**
     * 初始化图片懒加载功能。
     * 该函数通过监听窗口的滚动事件,来实现图片的延迟加载。当图片进入视口时,将其src属性设置为真正的图片源URL,从而实现懒加载的效果。
     */
    const imageLazyLoad = () => {
      // 获取页面中所有带有data-src属性的图片元素
      const imgs = document.querySelectorAll("img");
      // 定义计算函数,用于检查图片是否进入视口
      const calc = () => {
        imgs.forEach((img) => {
          // 检查图片是否进入视口:如果图片的顶部位置小于等于窗口的底部位置,则图片已进入视口,可以加载
          if (img.offsetTop <= window.innerHeight + window.scrollY)
            // 设置图片的src属性为data-src属性的值,真正开始加载图片
            img.src = img.dataset.src;
          else
            // 如果图片未进入视口,则返回,不进行加载
            return;
        });
      };
      // 监听窗口的滚动事件,以便在滚动时触发图片的加载
      window.addEventListener("scroll", () => calc());
      // 初次加载页面时,立即计算并加载可视区域内的图片
      calc();
    };
    // 调用函数,初始化图片懒加载
    imageLazyLoad();
  </script>
</body>

</html>

b. Intersection Observer API

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <style>
    img {
      width: 1000px;
      height: 700px;
      background-color: wheat;
      object-fit: cover;
      object-position: center;
    }
  </style>
</head>

<body>
  <img src="" data-src="./images/1.jpg" alt="" />
  <img src="" data-src="./images/2.jpg" alt="" />
  <img src="" data-src="./images/3.jpg" alt="" />
  <img src="" data-src="./images/4.jpg" alt="" />
  <img src="" data-src="./images/5.jpg" alt="" />
  <script>
    /**
     * 初始化图片懒加载
     * 该函数通过IntersectionObserver API来实现图片的懒加载。只有当图片进入视口时,才会真正加载图片资源
     */
    const imageLazyLoad = () => {
      // 获取所有需要懒加载的图片元素
      const imgs = document.querySelectorAll("img");

      // 创建IntersectionObserver实例,用于观察图片是否进入视口
      const observer = new IntersectionObserver((entries) => {
        // 遍历所有观察到的条目
        entries.forEach((entry) => {
          // 如果图片进入视口
          if (entry.isIntersecting) {
            let img = entry.target;
            // 设置图片的src属性为data-src属性的值,即真正的图片源地址
            img.src = img.dataset.src;
            // 停止观察该图片,因为它已经加载
            observer.unobserve(img);
          }
        });
      });

      // 对所有需要懒加载的图片元素启用IntersectionObserver观察
      imgs.forEach((img) => {
        observer.observe(img);
      });
    };
    imageLazyLoad();
  </script>
</body>

</html>

c. 第三方库

(3)CDN

  • 部署内容分发网络(CDN),将静态资源托管在地理位置接近用户的边缘节点上,减少延迟
  • 步骤:
    1. 选择 CDN 服务提供商,如 Amazon、Cloudflare 等
    2. 上传静态资源
    3. 配置 DNS
    4. 测试并优化
  • 优点:减少访问延迟、提高可用性、减轻源服务器负载

(4)缓存机制

  • 步骤:
    1. 设置 Cache-Control 头部:响应头字段,表示在缓存有效期内直接从本地缓存中加载这些资源,而不是向服务器发送请求
    2. 使用 ETag:响应头字段,表示资源的特定版本
      • 当浏览器再次请求资源时,会将 ETag 值发送给服务器,如果资源未更改(即 ETag 值相同),服务器将返回 304 Not Modified 响应,告诉浏览器使用本地缓存的版本。
    3. 配置 Expires 头部:兼容旧版浏览器
  • 优点:减少服务器负载、加快加载速度、改善用户体验

0x03 渲染性能优化

(1)减少 DOM 元素数量

避免不必要的 DOM 元素,以减少渲染和重绘的时间

  1. 使用 CSS 替代 DOM 元素

    举例:

    <ul>
      <li><img src="icon1.png" alt="Icon 1"><span>Item 1</span></li>
      <li><img src="icon2.png" alt="Icon 2"><span>Item 2</span></li>
    </ul>
    

    优化为

    <ul>
      <li class="item">Item 1</li>
      <li class="item">Item 2</li>
    </ul>
    
    <style>
    .item::before {
      content: "";
      display: inline-block;
      width: 20px;
      height: 20px;
      background-image: url(icon-based-on-class.png);
      background-size: cover;
      margin-right: 5px;
    }
    </style>
    
  2. 引入 Flex 布局与 Grid 布局

    举例:

    <div class="container">
      <div class="row">
        <div class="col">Item 1</div>
        <div class="col">Item 2</div>
      </div>
    </div>
    

    优化为

    <div style="display: flex;">
      <div>Item 1</div>
      <div>Item 2</div>
    </div>
    
  3. 多个动态内容采用 DocumentFragment 添加

    举例:

    for (let i = 0; i < 100; i++) {
      let li = document.createElement("li");
      li.textContent = `Item ${i}`;
      document.querySelector("ul").appendChild(li);
    }
    

    优化为

    let fragment = document.createDocumentFragment();
    for (let i = 0; i < 100; i++) {
      let li = document.createElement("li");
      li.textContent = `Item ${i}`;
      fragment.appendChild(li);
    }
    document.querySelector("ul").appendChild(fragment);
    

    DocumentFragment 是一个轻量级的文档对象,可以包含节点和子节点,但不会成为文档树的一部分

(2)事件委托

通过事件委托来减少与 DOM 的交互次数,提高性能

  • 事件委托:一种事件处理的技术,它利用事件冒泡的原理,只在父元素上设置一个事件监听器,而不是在每个子元素上分别设置。当子元素上发生事件时,该事件会冒泡到父元素,父元素上的事件监听器会检查事件源(即触发事件的子元素),并据此执行相应的操作

  • 举例

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8" />
    </head>
    
    <body>
      <ul>
        <li>Item 1</li>
        <li>Item 2</li>
      </ul>
      <button>Add item</button>
      <script>
        const ul = document.querySelector("ul");
    
        ul.addEventListener("click", (e) => {
          if (e.target.tagName === "LI") {
            console.log("e.target.textContent", e.target.textContent);
            e.target.remove();
          }
        });
    
        const button = document.querySelector("button");
        button.addEventListener("click", () => {
          const newItem = document.createElement("li");
          newItem.textContent = `Item ${ul.children.length + 1}`;
          ul.appendChild(newItem);
        });
      </script>
    </body>
    
    </html>
    

(3)优化 DOM 操作

使用批量更新、虚拟 DOM 等技术减少重绘和回流

  1. 批量更新:将多个 DOM 操作合并成一个批次,然后一次性执行(参考 DocumentFragment)
  2. 虚拟 DOM:一种编程概念,用 JavaScript 对象来表示 DOM 树
    • 当应用程序的状态发生变化时,虚拟 DOM 树会首先进行更新,然后使用高效的算法(如 diff 算法)来比较新旧虚拟 DOM 树之间的差异,并只将这些差异应用到真实的 DOM 上
    • 即检测发生变化的 DOM 元素,并仅对变化的 DOM 元素操作,减少不必要的 DOM 操作
    • 一般在 React、Vue 等前端框架中广泛应用

(4)CSS 放在顶部

  • 将CSS放在 <head> 标签内,以确保页面在加载时能够优先渲染样式

    <head>
      <link rel="stylesheet" href="style.css" />
    </head>
    

(5)异步加载 JavaScript

  • 将非首屏必需的 JS 脚本放在文档末尾或使用 asyncdefer 属性,避免阻塞渲染

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <!-- 同步加载首屏必需脚本 -->
      <script src="critical.js"></script>
    </head>
    
    <body>
      <!-- 页面内容 -->
    
      <!-- 异步加载非首屏且无依赖的脚本 -->
      <script async src="non-critical-async.js"></script>
    
      <!-- 异步加载但按顺序执行的非首屏脚本 -->
      <script defer src="non-critical-defer.js"></script>
    
      <!-- 将非首屏脚本放在底部(适用于不支持 async/defer 的老旧浏览器) -->
      <!-- <script src="non-critical-old-browser.js"></script> -->
    </body>
    
    </html>
    
  • asyncdefer

    • 两者都用于异步加载脚本
    • async:完全异步,脚本的加载和执行不会阻塞文档的解析,但多个异步脚本之间执行顺序不一定,不建议用于具有依赖关系的脚本
    • defer:等到整个文档都被解析和显示之后,再按照脚本在文档中出现的顺序来执行

0x04 网络性能优化

(1)启用 HTTP/2 或 HTTP/3

  • 启用 HTTP/2 或 HTTP/3,利用多路复用、头部压缩等特性提升请求效率
  • 一般在 Web 服务器中,通过 Nginx、Apache 等配置并启用

(2)TLS/SSL

(3)预加载和预读取

a. 预加载

  • 预加载:一种资源提示,它告诉浏览器这个资源对于当前导航立即需要,并且应该被优先下载和解析

    • 如字体、CSS 和关键 JavaScript 脚本
  • 使用 <link rel="preload"> 预加载关键资源

    <head>
      <!-- 预加载字体 -->
      <link rel="preload" href="fonts/myfont.woff2" as="font" type="font/woff2" crossorigin="anonymous">
    
      <!-- 预加载CSS -->
      <link rel="preload" href="styles/main.css" as="style">
    
      <!-- 其他头部标签... -->
    </head>
    

b. 预读取

  • 预读取:一种资源提示,它告诉浏览器这个资源可能会在将来的导航中被用到,但不像预加载那样具有紧迫性

    • 浏览器可以选择在空闲时间下载这些资源,以便在用户实际需要它们时能够更快地加载
  • 使用 <link rel="prefetch"> 预读取可能需要的未来资源

    <link rel="prefetch" href="details/product-images.jpg">
    <link rel="prefetch" href="details/product-details.js"> 
    

(4)本地缓存

  • 存储限制:存储大小限制在 4KB 左右,且存储数量有限制
  • 缺点:
    1. 每次都会将 Cookie 数据携带在 HTTP 请求中,可能带来性能问题占用带宽
    2. 安全性较低
  • 由于浏览器的跨域限制,客户端和服务端必须保证同源原则
  • 场景:跟踪用户会话信息,如用户登录状态、购物车信息等
  • Session:以键值对的方式将缓存数据保存在服务器中,并把键值(Session ID)作为 Cookie 返给浏览器
  • Token:应对移动互联网不提供 Cookie 的解决方案,将数据哈希加密并保存在移动端的存储系统中
    • JWT 是一种广泛应用的 Token 标准

b. LocalStorage

  • HTML5 提供的一种新的本地缓存方案,用于存储数据在用户的浏览器中
  • 存储限制:
    • 长久保存,无有效期,直到手动删除或浏览器清理缓存为止
    • 存储空间一般可以达到 5MB 及以上(不同的浏览器有所区别)
  • 场景:存储需要在多个页面或会话中持久保存的数据,如用户偏好设置、游戏进度等

c. SessionStorage

  • 大体与 LocalStorage 类似,在存储限制上,数据仅在当前会话期间有效,浏览器关闭或标签页关闭后数据即被清除
  • 场景:存储仅在当前会话中需要的数据,如临时状态信息、表单数据等

d. IndexedDB

  • 一个低级的 API,允许进行复杂的查询、事务处理和数据库管理操作,提供索引功能
  • 存储限制:存储空间相对较大,可以存储大量数据
  • 场景:存储大量结构化数据并需要进行复杂查询,如离线应用、游戏数据存储等

e. Cache API

  • 一种用于存储和检索网络请求的响应的接口

  • 可以与 Service Workers 结合使用,实现离线应用和性能优化

    Service Workers:在 Web 浏览器中运行的脚本,具备在后台独立于网页运行的能力

    • 提供很多高级功能,如离线内容缓存、推送通知、背景数据同步等
  • 场景:精确控制缓存策略和资源缓存,如构建 PWA(Progressive Web Apps)时

0x05 框架与库的选择与优化

(1)框架

  • 轻量级

    • Preact:3kb 大小的 React 轻量、快速替代方案,拥有相同的现代 API
    • Vue3:在 Vue2 基础上,应用 Tree-shaking,允许在构建过程中自动移除未使用的代码
  • 按需加载

    • 采用代码分割懒加载技术,将应用拆分成多个小块,并在需要时才加载它们

    • 以 Vue Router 为例,Vue Router 支持动态导入

      const routes = [
        {
          path: "/products",
          name: "Products",
          // 使用动态导入来懒加载组件
          component: () =>
            import(/* webpackChunkName: "products" */ "./views/Products.vue"),
        },
        // 其他路由...
      ];
      

(2)第三方库

  • 仅引入必要的库,避免过时或冗余的库,定期检查更新以利用性能优化

0x06 性能监控与优化工具

(1)性能分析工具

  • Lighthouse:Google 开发的一款开源自动化工具,集成在 Chrome DevTools 中
  • PageSpeed Insights:Google 提供的一款免费工具,官网链接
  • Chrome DevTools Performance 面板:集成在 Chrome DevTools 中

(2)用户性能监控

采用集成 RUM(Real User Monitoring)工具收集实际用户的加载性能数据

  • Google Analytics:使用插件 Google Tag Manager 来部署 RUM 脚本,该脚本将收集用户交互数据并发送到 Google Analytics 进行处理和分析
  • SpeedCurve:https://www.speedcurve.com/

0x07 其他优化策略

(1)首屏内容优化

  • 确保首屏加载时立即展示关键内容,避免用户看到空白或加载指示器过久
  • 举例:电商网站首屏优化操作
    • 精简首屏内容:保留最关键的内容,如网站Logo、欢迎语、轮播图、商品推荐
    • 优化图片和脚本:压缩和优化所有首屏加载的图片,合并和压缩 CSS 和 JavaScript 脚本进行,减少HTTP请求次数
    • 异步加载非关键内容:非首屏关键内容设置为异步加载,即用户滚动到相应位置时再加载这些内容
    • 使用CDN加速:将网站内容分发到全球多个 CDN 节点,根据用户地理位置选择最近的节点进行加载
    • 预加载和缓存:利用浏览器的预加载和缓存机制,提前加载和存储一些常用的资源文件

(2)语义化 HTML

  • 使用合理 HTML 标记以及其特有的属性去格式化文档内容,提高内容可理解性

  • 详细方法参考:HTML语义化 | CSDN-北航程序员小陈

  • 举例:

    <!DOCTYPE html>
    <html lang="zh-CN">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>新闻文章标题</title>
    </head>
    
    <body>
      <header>
        <h1>新闻文章标题</h1>
        <p>由 <a href="/author-profile">作者姓名</a> 发表于 <time datetime="2023-04-01">2023年4月1日</time></p>
      </header>
    
      <main>
        <article>
          <h2>引言</h2>
          <p>这里是引言部分的内容,简要介绍文章的主题和背景。</p>
    
          <h2>正文标题</h2>
          <p>这里是正文的第一段,详细阐述文章的主要观点或故事。</p>
    
          <h3>小节标题</h3>
          <p>这里是文章中的一个小节,进一步细化或支持主要观点。</p>
    
          <!-- 可以继续添加更多的h2、h3、p等元素来构建文章内容 -->
    
          <footer>
            <p>文章结束。</p>
          </footer>
        </article>
      </main>
    
      <aside>
        <h2>相关文章</h2>
        <ul>
          <li><a href="/article1">相关文章1</a></li>
          <li><a href="/article2">相关文章2</a></li>
          <!-- 更多相关文章链接 -->
        </ul>
      </aside>
    
      <footer>
        <p>版权所有 &copy; 2023 网站名称</p>
      </footer>
    </body>
    
    </html>
    

(3)元数据

  • 设置 <title><meta><link rel="canonical"> 等 SEO 相关标签

  • 举例:

    <!DOCTYPE html>
    <html lang="zh-CN">
    
    <head>
      <!-- 页面编码,用于处理不同语言的字符串 -->
      <meta charset="UTF-8" />
    
      <!-- 设置视口,确保网页在不同设备上正确显示 -->
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    
      <!-- 页面标题,显示在浏览器标签和搜索结果中 -->
      <title>页面标题 - 网站名称</title>
    
      <!-- 页面描述,显示在搜索结果中,用于概括页面内容 -->
      <meta name="description" content="这里是页面的简短描述,应包含关键词并吸引用户点击。" />
    
      <!-- 页面关键词,虽然现代搜索引擎对keywords标签的重视程度降低,但仍可作为参考 -->
      <meta name="keywords" content="关键词1, 关键词2, 关键词3" />
    
      <!-- 指定页面的规范URL,有助于防止内容重复被搜索引擎索引 -->
      <link rel="canonical" href="https://www.example.com/your-page-url" />
    
      <!-- 其他可能的元数据 -->
      <meta name="author" content="作者姓名或组织" /> <!-- 添加作者信息 -->
      <meta name="robots" content="index, follow" /> <!-- 指示搜索引擎索引并跟踪页面上的链接 -->
    
      <!-- 对于响应式网站,可以使用meta标签来适应不同设备 -->
      <meta name="HandheldFriendly" content="true" /> <!-- 告诉移动设备,页面适合于手机浏览 -->
      <meta name="MobileOptimized" content="320" /> <!-- 指定移动设备的屏幕宽度,以适应响应式设计 -->
    
      <!-- 引入CSS样式 -->
      <link rel="stylesheet" href="style.css" />
    </head>
    
    <body>
      <!-- 页面内容 -->
      <!-- 引入JavaScript脚本 -->
      <script src="script.js"></script>
    </body>
    
    </html>
    

(4)结构化数据

  • 结构化数据:一种使用特定格式(如 JSON-LD、Microdata 或 RDFa)来标记网页内容的方式,以便搜索引擎和其他机器能够更容易地理解和处理这些信息

  • 举例:以下是采用 Schema.org 和JSON-LD 格式的结构化数据

    <!DOCTYPE html>
    <html lang="zh-CN">
    
    <head>
      <meta charset="UTF-8" />
      <title>电影《星际穿越》</title>
      <script type="application/ld+json">
        {  
          "@context": "https://schema.org/",  
          "@type": "Movie",  
          "name": "星际穿越",  
          "image": "https://example.com/movie-poster.jpg",  
          "director": {  
            "@type": "Person",  
            "name": "克里斯托弗·诺兰"  
          },  
          "genre": ["科幻", "剧情", "冒险"],  
          "actor": [  
            {  
              "@type": "Person",  
              "name": "马修·麦康纳希"  
            },  
            {  
              "@type": "Person",  
              "name": "安妮·海瑟薇"  
            }  
          ],  
          "datePublished": "2014-11-07",  
          "description": "一队探险家利用他们针对虫洞的新发现,超越人类对于太空旅行的极限,从而开始在广袤的宇宙中进行星际航行的故事。",  
          "aggregateRating": {  
            "@type": "AggregateRating",  
            "ratingValue": "8.7",  
            "reviewCount": "123456"  
          }  
        }  
        </script>
    </head>
    
    <body>
      <!-- 网页内容 -->
      <h1>电影《星际穿越》</h1>
      <p>导演:克里斯托弗·诺兰</p>
      <p>主演:马修·麦康纳希, 安妮·海瑟薇</p>
      <p>类型:科幻, 剧情, 冒险</p>
      <p>上映日期: 2014年11月7日</p>
      <p>剧情简介:...(详细描述)</p>
    </body>
    
    </html>
    

(5)无障碍性(a11y)

  • 无障碍性:确保网站对所有用户,包括残障用户,都能够友好地访问和使用
  • WCAG 标准:Web 内容无障碍指南
  • 举例:
    1. 非文本内容的文本替代
      • 如:<img alt="这是一张图片" />
    2. 键盘可访问
    3. 清晰和一致的导航
    4. 足够的颜色对比度
    5. 字幕和音频描述

-End-

posted @ 2024-07-26 15:51  SRIGT  阅读(222)  评论(2编辑  收藏  举报