前端性能优化到底该怎么做(下)- 直捣黄龙

303次阅读  |  发布于1年以前

前言

在上一篇 **[前端性能优化到底该怎么做(上)— 开门见山] **[2] 一文中介绍了和前端性能优化相关的一些前置知识,那么本篇就针对优化方案进行总结,核心的方向还是上篇文章中提到的内容:

其实将上述两点再进行翻译,那么其实指的就是 网络层面的优化浏览器层面的优化,这样看来其实前端性能优化方向还是很明确的,只不过明确的方向中还是会涉及不同方面的具体优化手段。

0B249F1E.jpg

还是不得不回顾 从输入 URL 到页面加载完成 的核心过程:

从上述的内容来看,不难发现每一步都是需要消耗一定的时间,那么优化的方向就可以围绕着这些内容来考虑。

长文预警❗️❗️❗️ 长文预警❗️❗️❗️ 长文预警❗️❗️❗️ 长文预警❗️❗️❗️

1.gif

如何保证资源更快的加载速度?

下面内容主要针对 DNS 解析、**TCP 连接HTTP 请求/响应** 等阶段来谈的优化,核心优化核心其实就是 网络层面

使用 dns-prefetch 减少 DNS 的查询时间

dns-prefetch 能够 提前解析 后续可能会用到的 不同域的域名,使解析结果 缓存到系统缓存 中,缩短 DNS 解析时间以提高网站的访问速度。

比如在掘金中的体现如下:

扩展DNS 解析的核心过程

当浏览器访问一个域名时需解析一次 DNS,以获得对应域名的 IP 地址:

使用 preconnect 提前建立连接

preconnect 的作用是提前和第三方资源建立连接,设置了它浏览器就会做好早期的连接工作,但这个连接通常只会维持 10 s

比如在当前域请求一个资源前,可能会涉及 DNS 寻址、TLS 握手、TCP 握手、重定向等,这过程也会花费一定的时间。

比如在掘金中的体现如下:

使用 preload / prefetch 预先加载资源

preload

preload 的作用是提前加载页面对应的 关键资源 加快页面的渲染,preload 的优先级顺序和 as 属性相关,**具体可见**[3]。

注意as 属性一定要设置,除了上面提到的设置优先级外,还涉及到浏览识别的问题:如果没有设置 as 属性,后续遇到该请求就会被作为一个 XHR 请求,把意味着资源预加载的功能可能会失效,因为可能会每次都发起新的请求获取

比如在掘金中的体现如下:

比如在 vue-cli 的默认 webpack 配置,如下:

image.png

prefetch

preload 是对资源的预加载,它虽提前加载但只在需要执行时执行,即这个资源一定是当前页面所需要的资源,如果是需要为下一个页面提前加载资源,那么应该使用 prefetch,它会在 浏览器空闲时 下载资源。

比如在 vue-cli 的默认 webpack 配置,如下:

image.png

压缩资源体积

资源是需要通过 http 数据包的方式在网络中进行传输的,那么只要能减少传输数据包的体积,也是能够使得资源更快到达客户端,这也是压缩资源体积的核心目的。

HTTP 压缩

HTTP 压缩中一个典型代表就是 gzip,它是一种优秀的压缩算法,可对 http 请求中的一些文件资源进行压缩处理,一般来讲是要在服务端处理的,可通过在响应头中设置 Content-encoding: gzip 表示当前资源使用的压缩方式(如:gzip、deflate、br 等),便于客户端使用正确的方式解压。

注意gzip 并不是万能的,它不能保证针对每个文件的压缩都能使其体积变小,关于 Content-Encoding 的内容 **可点此查阅**[4],或者可参考 **content-encoding 除了gzip之外,你还知道哪些?**[5]

比如在京东中的体现:

image.png

比如在掘金中的体现:

image.png

Webpack 压缩

HTTP 压缩 不就够了吗?为什么还需要 Webpack 压缩?

首先必须要明确的是压缩的过程本身就是会消耗时间的,如果所有资源都等到被访问的时候再由服务端进行压缩,在压缩完成之前客户端还是得处于等待状态,即仍 不能保证资源以最快的速度到达客户端

那么优化方案就是将压缩资源的时间放到打包构建中,毕竟只有真正需要发布线上生产环境时才需要执行一系列的打包优化的操作,而这相比于 http请求/响应 速度,稍微延长产物打包时间没有什么大问题。

下面会列举一些 Webpack 插件,但并不会去讲其中的具体用法,因为这些只是达到目的的不同方案而已,每个方案要是细讲都可以独占一篇文章,在这是没有必要的,具体用法可自行查阅。

使用 CompressionPlugin 压缩文件

image.png

**webpack 文档**[6] 提供插件合集中就包含了该插件,它的作用就是:**Prepare compressed versions of assets to serve them with Content-Encoding.**

使用 HtmlWebpackPlugin 压缩 HTML 文件

image.png

通常我们需要 **HtmlWebpackPlugin**[7] 插件来生成对应 HTML 或 对已有的 HTML 模板自动注入 webpack bundles 资源,除此之外,它还可配置 minify 选项实现压缩模板的目的。

可以在 vue 项目下执行 vue inspect \--mode production > webpack.config.js 来查看脚手架的默认 webpack 配置内容,比如:

image.png

使用 SplitChunksPlugin 自定义分包策略

Webpack 默认会将尽可能多的模块代码打包在一起,这种默认规则的带来的优点和缺点都很明显:

**SplitChunksPlugin**[8] 是 Webpack 4 之后内置实现的最新分包方案,与 Webpack 3 中的 CommonsChunkPlugin 相比,它能够基于一些更灵活、合理的启发式规则将 Module 编排进不同的 Chunk,最终构建出 性能更佳、缓存更友好 的应用产物。

比如在 vue-cli 的默认 webpack 配置,如下:

image.png

使用 MiniCssExtractPlugin 抽离和压缩 CSS

**MiniCssExtractPlugin**[9] 会将 CSS 提取到单独的文件中,为每个包含 CSSJS 文件创建一个 CSS 文件,并且支持 CSSSourceMaps按需加载

比如在 vue-cli 的默认 webpack 配置,如下:

image.png

使用 ImageMinimizerWebpackPlugin 压缩图片资源

图片仍是一个 Web 应用中的必不可少的资源,而图片资源的体积也是首屏页面加载的瓶颈之一,因此,压缩图片也是性能优化需要考虑的内容。

**ImageMinimizerWebpackPlugin**[10] 可用于使用 优化/压缩 所有图像,它可以支持 无损(不损失质量)有损(质量下降) 两种模式的压缩方式。

image.png

通过 Tree Shaking 移除无用代码

**Tree Shaking**[11] 依赖于 ES6 模块语法的 **静态结构**[12] 特性(如: import[13] 和 export[14]),当 webpack 的模式 mode"production" 时,就可以启用 **更多优化项**[15],包括 压缩代码Tree Shaking

但同时我们就必须保证:

减少 http 请求数量

不同协议请求数量 仍然可能成为 请求/响应 慢的原因:

减少不必要的 cookie

不必要的 cookie 来回传输会造成带宽浪费:

CDN 托管静态资源 + HTTP 缓存

CDN 加速的本质是缓存加速,将服务器上存储的静资源容缓存在 CDN 节点上,当后续访问这些静态内容时,无需访问服务器源站,选择就近访问 CDN 节点即可,从而达到加速的效果,同时减轻服务器源站的压力。

在掘金中体现如下:

image.png

协议升级为 Http2.0

http1.x 存在的问题:HTTP 的底层协议是 TCP,而 TCP 是面向连接即需要 三次握手 才能建立连接,其中:

以上问题 http2.0 都能够解决:

比如在掘金中的体现:

image.png 如何保证视图更快的渲染和交互?

保证资源快速到达客户端后,接下来就需要针对 浏览器的解析和渲染 进行优化,当然还包括后续的页面交互的优化,这其实就是 浏览器层面 的优化。

浏览器渲染 HTML 文件的核心过程:

渲染层面

减少阻塞渲染的因素

真正渲染视图之前,必然要生成 DOM Tree 和 **CSSOM**,因此必须保证 HTML 解释器CSS 解释器 都尽早处理完成,同时 JavaScript 的加载和执行可能会阻塞这个过程:

懒加载

懒加载主要是针对数量大、资源加载慢的情况,比如图片资源、大量列表数据展示等:

白屏优化

白屏是由于 SPA 应用需要等待 JavaScript 加载并执行完成后才会生成具体的页面结构内容导致的,即初始化模板中没有任何有意义需要被渲染的 HTML 结构:

服务端渲染(server-side rendering)

现代框架默认是属于客户端应用框架,即组件的代码会在浏览器中运行,然后向页面输出 DOM 元素,也叫 客户端渲染(client-side rendering,CSR)

服务端渲染(server-side rendering,SSR) 可将相同组件在服务渲染成相应的 HTML 字符串,并发送给浏览器进行渲染,即客户端不需要等待所有的 JavaScript 都被下载并执行之后才显示,所以用户可以更快看到完整的渲染好的内容。

预渲染(prerender)

上述 服务端渲染(server-side rendering,SSR) 虽然能够解决一些客户端存在的问题,但它也带来了别的问题:

因此,并不是所有应用都合适 服务端渲染,如果只是希望通过 SSR 来改善一些 推广页面 (如 //about/contact 等) 的 SEO,那么应该优先考虑 预渲染 的方式:

交互层面

减少回流/重绘

重绘:页面中元素样式的改变并不影响它在文档流中的位置时(如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并 重新绘制

回流:当 Render Tree 中部分或全部元素的 尺寸、结构、某些属性 发生改变时,浏览器 重新渲染 部分或全部文档

防抖/节流

防抖:多次频繁触发执行操作,以 最后一次 为准,忽略中间过程

节流:在指定的时间间隔内,只允许 执行一次对应的操作

合理使用 防抖/节流 优化应用中的操作,比如 节流 可用于优化 滚动事件、模糊搜索等,**防抖** 可用于优化一些按钮点击操作等。

Web Worker

JavaScript 是单线程的,如果存在需要大量计算的场景(如视频解码),UI 线程就会被阻塞,甚至浏览器直接卡死。

Web Worker 可以使脚本运行在新的线程中,它们独立于主线程,可以进行大量的计算活动,而不会影响主线程的 UI 渲染,但不能滥用 Web Worker

虚拟列表

最常用的还是 分页加载 的方式:

虚拟列表 核心就是固定渲染的 DOM 数,通过动态切换数据内容实现视图的更新,并保证文档中真实 DOM 的数量不随着数据量增大而增大(其实和 table 分页很像,但它支持滚动)。

想了解其核心实现的,可查看 **虚拟滚动是怎么做性能优化的\?**[19]

大文件分片上传

大部分的项目总少不了文件上传功能,但对大文件的上传还是有必要进行优化,所谓的 断点续传秒传 都要基于 分片上传 这个核心功能。

想了解其核心实现的,可查看 **请问:怎么实现大文件快速上传?**[20]

Excel 导入/导出

针对 Excel 导入/导出 的功能相信很多人第一印象是后端的活,但大多数情况下,后端接口的处理速度会受各种影响,导致速度方面不是很理想,有时候也是需要前端来进行优化处理的,比如导入时前端不发送文件只发送解析后的 JSON 数据,导出时不需要单独发送额外接口,直接使用当前展示数据实现导出等。

想了解其核心实现的,可查看 **给我实现一个前端的 Excel 导入和导出功能**[21]

Vue 项目的优化

这部分内容相信大家都不陌生,下面就简单列举一些内容(包括但不限于):

基于 vue.config.jswebpack 进行优化,具体可见 **如何优化你的 vue-cli 项目?**[22]

总结

以上优化方案对应到 **上一篇**[23] 中提到的性能指标,如下:

关键资源越早到达客户端,证明 TTFB 时间越短,而这也能间接的减少 FPFCP 的时间;对资源进行了压缩处理意味着能够尽可能提升 LCP 的时间;减少了页面的 回流/重绘 就能使得 CLS 的数值越小,视图越趋于稳定;FID 是一个用于跟踪浏览器对用户输入做出反应之前的延迟时间的指标,包括点击和敲击,保证资源的快速加载和页面尽早渲染,其对应的数值就越小,视图响应就越快。

最后

前端性能优化 的范围实在太大,以上列举的优化主要围绕着 资源加载、页面渲染/交互 两个大的方向,而具体的优化方案其实有很多(包括但不限于上述内容),很多内容随着关注的方向不同而不同。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8