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

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-

发表评论

评论已关闭。

相关文章