前言
在上一篇 前端性能优化到底该怎么做(上)— 开门见山 一文中介绍了和前端性能优化相关的一些前置知识,那么本篇就针对优化方案进行总结,核心的方向还是上篇文章中提到的内容:
保证资源更快的 加载速度
:达到越快渲染越快,视图展现就越快保证视图更快的 渲染速度/交互速度
:用户与页面交互,前提是页面要渲染出来,其次是页面需要尽早反馈,目的就是保证用户良好的体验性
其实将上述两点再进行翻译,那么其实指的就是 网络层面的优化 和 浏览器层面的优化,这样看来其实前端性能优化方向还是很明确的,只不过明确的方向中还是会涉及不同方面的具体优化手段。
还是不得不回顾 从输入 URL
到页面加载完成 的核心过程:
- 进行
DNS
解析 - 建立
TCP
连接 - 客户端发送
HTTP
请求 - 服务端响应
HTTP
资源 - 浏览器获取响应内容,进行解析和渲染
从上述的内容来看,不难发现每一步都是需要消耗一定的时间,那么优化的方向就可以围绕着这些内容来考虑。
长文预警❗️❗️❗️ 长文预警❗️❗️❗️ 长文预警❗️❗️❗️ 长文预警❗️❗️❗️
如何保证资源更快的加载速度?
下面内容主要针对 DNS
解析、TCP
连接、HTTP
请求/响应 等阶段来谈的优化,核心优化核心其实就是 网络层面。
使用 dns-prefetch
减少 DNS 的查询时间
dns-prefetch
能够 提前解析 后续可能会用到的 不同域的域名,使解析结果 缓存到系统缓存 中,缩短 DNS
解析时间以提高网站的访问速度。
比如在掘金中的体现如下:
【扩展】
DNS
解析的核心过程
当浏览器访问一个域名时需解析一次 DNS
,以获得对应域名的 IP
地址:
- 浏览器 会从自身的 缓存中 查询是否存在对应域名的
IP
地址,若存在返回,若不存在进入下一步 - 客户机查询操作系统中的
/etc/hosts
文件中查询是否有对应域名的IP
地址,若存在则返回,若不存在进入下一步 - 客户机请求 本地域名服务器(LDNS) 进行解析,本地域名服务器收到客户机的请求后,先查询自己的缓存信息是否有对应域名的
IP
地址,若存在返回结果,没有则进行下一步 - 本地域名服务器请求 根域名服务器 解析该域名,根域名告诉本地域名服务器去找对应的 一级域名服务器
- 本地域名服务器请求一级域名服务器解析这个域名,一级域名服务器告诉它去找对应的 二级域名服务器
- 本地域名服务器请求二级域名服务器解析这个域名,二级域名服务器告诉它去找对应的 子域名服务器
- 本地域名服务器请求子域名服务器解析这个域名,子域名服务器返回对应的 IP 地址
- 本地域名服务器将
IP
地址记录到缓存中,并返回给客户机(会缓存起来),客户机根据收到的IP
地址访问该网站
使用 preconnect
提前建立连接
preconnect
的作用是提前和第三方资源建立连接,设置了它浏览器就会做好早期的连接工作,但这个连接通常只会维持 10 s
。
比如在当前域请求一个资源前,可能会涉及 DNS
寻址、TLS
握手、TCP
握手、重定向等,这过程也会花费一定的时间。
比如在掘金中的体现如下:
使用 preload / prefetch
预先加载资源
preload
preload
的作用是提前加载页面对应的 关键资源 加快页面的渲染,preload
的优先级顺序和 as
属性相关,具体可见。
【注意】
as
属性一定要设置,除了上面提到的设置优先级外,还涉及到浏览识别的问题:如果没有设置as
属性,后续遇到该请求就会被作为一个XHR
请求,把意味着资源预加载的功能可能会失效,因为可能会每次都发起新的请求获取
比如在掘金中的体现如下:
比如在 vue-cli
的默认 webpack
配置,如下:
prefetch
preload
是对资源的预加载,它虽提前加载但只在需要执行时执行,即这个资源一定是当前页面所需要的资源,如果是需要为下一个页面提前加载资源,那么应该使用 prefetch
,它会在 浏览器空闲时 下载资源。
比如在 vue-cli
的默认 webpack
配置,如下:
压缩资源体积
资源是需要通过 http
数据包的方式在网络中进行传输的,那么只要能减少传输数据包的体积,也是能够使得资源更快到达客户端,这也是压缩资源体积的核心目的。
HTTP 压缩
HTTP 压缩中一个典型代表就是 gzip
,它是一种优秀的压缩算法,可对 http
请求中的一些文件资源进行压缩处理,一般来讲是要在服务端处理的,可通过在响应头中设置 Content-encoding: gzip
表示当前资源使用的压缩方式(如:gzip、deflate、br
等),便于客户端使用正确的方式解压。
【注意】
gzip
并不是万能的,它不能保证针对每个文件的压缩都能使其体积变小,关于Content-Encoding
的内容 可点此查阅,或者可参考 content-encoding 除了gzip之外,你还知道哪些?
比如在京东中的体现:
比如在掘金中的体现:
Webpack 压缩
有 HTTP
压缩 不就够了吗?为什么还需要 Webpack
压缩?
首先必须要明确的是压缩的过程本身就是会消耗时间的,如果所有资源都等到被访问的时候再由服务端进行压缩,在压缩完成之前客户端还是得处于等待状态,即仍 不能保证资源以最快的速度到达客户端。
那么优化方案就是将压缩资源的时间放到打包构建中,毕竟只有真正需要发布线上生产环境时才需要执行一系列的打包优化的操作,而这相比于 http
的 请求/响应 速度,稍微延长产物打包时间没有什么大问题。
下面会列举一些 Webpack 插件,但并不会去讲其中的具体用法,因为这些只是达到目的的不同方案而已,每个方案要是细讲都可以独占一篇文章,在这是没有必要的,具体用法可自行查阅。
使用
CompressionPlugin
压缩文件
webpack
文档 提供插件合集中就包含了该插件,它的作用就是:Prepare compressed versions of assets to serve them with Content-Encoding.
使用
HtmlWebpackPlugin
压缩HTML
文件
通常我们需要 HtmlWebpackPlugin
插件来生成对应 HTML
或 对已有的 HTML
模板自动注入 webpack bundles
资源,除此之外,它还可配置 minify
选项实现压缩模板的目的。
可以在 vue
项目下执行 vue inspect --mode production > webpack.config.js
来查看脚手架的默认 webpack
配置内容,比如:
使用
SplitChunksPlugin
自定义分包策略
Webpack
默认会将尽可能多的模块代码打包在一起,这种默认规则的带来的优点和缺点都很明显:
- 优点:能减少最终页面的
HTTP
请求数 - 缺点:
- 页面初始代码包过大,影响首屏渲染性能
- 无法有效应用浏览器缓存
SplitChunksPlugin
是 Webpack 4
之后内置实现的最新分包方案,与 Webpack 3
中的 CommonsChunkPlugin
相比,它能够基于一些更灵活、合理的启发式规则将 Module
编排进不同的 Chunk
,最终构建出 性能更佳、缓存更友好 的应用产物。
比如在 vue-cli
的默认 webpack
配置,如下:
使用
MiniCssExtractPlugin
抽离和压缩CSS
MiniCssExtractPlugin
会将 CSS
提取到单独的文件中,为每个包含 CSS
的 JS
文件创建一个 CSS
文件,并且支持 CSS
和 SourceMaps
的 按需加载 。
比如在 vue-cli
的默认 webpack
配置,如下:
使用
ImageMinimizerWebpackPlugin
压缩图片资源
图片仍是一个 Web
应用中的必不可少的资源,而图片资源的体积也是首屏页面加载的瓶颈之一,因此,压缩图片也是性能优化需要考虑的内容。
ImageMinimizerWebpackPlugin
可用于使用 优化/压缩 所有图像,它可以支持 无损(不损失质量)、有损(质量下降) 两种模式的压缩方式。
通过
Tree Shaking
移除无用代码
Tree Shaking
依赖于 ES6
模块语法的 静态结构 特性(如: import
和 export
),当 webpack
的模式 mode
为 "production"
时,就可以启用 更多优化项,包括 压缩代码 与 Tree Shaking。
但同时我们就必须保证:
- 尽量使用
ES6
模块语法,即import
和export
- 保证没有 编译器(如:
babel
)将对应的ES6
模块语法转换为CommonJS
的语法(如:@babel/preset-env
的默认行为) - 可在项目的
package.json
文件中添加"sideEffects"
属性,标识当前内容是否存在副作用操作 - 可在通过
/*#__PURE__*/
注释,将函数调用标记为无副作用