前端性能优化实践方向与方法
0x01 代码优化与压缩
(1)HTML
移除不必要的空白字符、注释和冗余标签,以减少文件大小
使用命令
npm install html-minifier -g
安装
HTML Minifier使用命令
html-minifier -V
确认安装成功在 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);
使用命令
node .\index.js
运行,输出结果为:
<p title=blah id=moo>foo</p>
详细参考:
https://www.npmjs.com/package/html-minifier
在线使用:
https://kangax.github.io/html-minifier/
(2)CSS
精简样式表,避免使用冗余或过时的属性,合理组织选择器以减少计算复杂度
使用命令
npm install cssnano postcss postcss-cli --save-dev
安装 PostCSS 与 CSSNaNo在 Node.js 环境中配置 postcss.config.js
module.exports = { plugins: [ require("cssnano")({ preset: "default", }), ], };
使用命令
npx postcss input.css > output.css
运行,生成优化后的结果 output.css其他工具:
- PostCSS:
https://www.postcss.com.cn/ - PurgeCSS:
https://www.purgecss.cn/
- PostCSS:
(3)JavaScript
使用工具(如 Webpack 等)或在线服务对代码进行压缩,移除空格、注释和不必要的字符
使用命令
npm install terser-webpack-plugin --save-dev
安装 terser-webpack-plugin在 Node.js 环境中配置 webpack.config.js
const TerserPlugin = require("terser-webpack-plugin"); module.exports = { optimization: { minimize: true, minimizer: [new TerserPlugin()], }, };
使用命令
npx webpack
运行 Webpack 打包工具,并优化 JavaScript 代码
(4)合并文件
将多个 CSS、JavaScript 文件合并为一个,减少 HTTP 请求的数量
- 在 Webpack 中,可以通过配置
entry
和
output
选项来自动合并多个模块到一个文件中 - 在 Gulp 中,可以使用
gulp-concat
插件来合并文件
0x02 静态资源优化
(1)压缩图片
- TinyPNG:
API Reference - ImageOptim:
Optimize on the fly - imagemin:
https://www.npmjs.com/package/imagemin
(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. 第三方库
- 如
vue-lazyload
(Vue 框架中使用)
(3)CDN
- 部署内容分发网络(CDN),将静态资源托管在地理位置接近用户的边缘节点上,减少延迟
- 步骤:
- 选择 CDN 服务提供商,如 Amazon、Cloudflare 等
- 上传静态资源
- 配置 DNS
- 测试并优化
- 优点:减少访问延迟、提高可用性、减轻源服务器负载
(4)缓存机制
- 步骤:
- 设置 Cache-Control 头部:响应头字段,表示在缓存有效期内直接从本地缓存中加载这些资源,而不是向服务器发送请求
- 使用 ETag:响应头字段,表示资源的特定版本
- 当浏览器再次请求资源时,会将 ETag 值发送给服务器,如果资源未更改(即 ETag 值相同),服务器将返回 304 Not Modified 响应,告诉浏览器使用本地缓存的版本。
- 配置 Expires 头部:兼容旧版浏览器
- 优点:减少服务器负载、加快加载速度、改善用户体验
0x03 渲染性能优化
(1)减少 DOM 元素数量
避免不必要的 DOM 元素,以减少渲染和重绘的时间
使用 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>
引入 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>
多个动态内容采用 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 等技术减少重绘和回流
- 批量更新
:将多个 DOM 操作合并成一个批次,然后一次性执行(参考 DocumentFragment) - 虚拟 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 脚本放在文档末尾或使用
async
、
defer
属性,避免阻塞渲染<!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>
async
与
defer
- 两者都用于异步加载脚本
async
:完全异步,脚本的加载和执行不会阻塞文档的解析,但多个异步脚本之间执行顺序不一定,不建议用于具有依赖关系的脚本defer
:等到整个文档都被解析和显示之后,再按照脚本在文档中出现的顺序来执行
0x04 网络性能优化
(1)启用 HTTP/2 或 HTTP/3
- 启用 HTTP/2 或 HTTP/3,利用多路复用、头部压缩等特性提升请求效率
- 一般在 Web 服务器中,通过 Nginx、Apache 等配置并启用
(2)TLS/SSL
- 确保网站使用 HTTPS 加密传输,提高安全性并可能获得 SEO 优势
- HTTPS、TLS/SSL 在网络传输的应用参考:
浏览器工作过程及相关名词 | 博客园-SRIGT
(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)本地缓存
a. Cookie
- 存储限制:存储大小限制在 4KB 左右,且存储数量有限制
- 缺点:
- 每次都会将 Cookie 数据携带在 HTTP 请求中,可能带来
性能问题
并
占用带宽 - 安全性较低
- 每次都会将 Cookie 数据携带在 HTTP 请求中,可能带来
- 由于浏览器的跨域限制,客户端和服务端必须保证同源原则
- 场景:跟踪用户会话信息,如用户登录状态、购物车信息等
- 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)框架
轻量级
按需加载
采用
代码分割
和
懒加载
技术,将应用拆分成多个小块,并在需要时才加载它们以 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>版权所有 © 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 内容无障碍指南 - 举例:
- 非文本内容的文本替代
- 如:
<img alt="这是一张图片" />
- 如:
- 键盘可访问
- 清晰和一致的导航
- 足够的颜色对比度
- 字幕和音频描述
- 非文本内容的文本替代
-End-