前端性能优化实战

3,876 阅读12分钟

本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力

前端性能优化,面试中常说的那些优化手段都是基本操作,基本来自雅虎军规,好多年没变,因为也是主要的优化大头(就是通过这些手段达到明显效果的优化);非基本操作也就是各种细节,可能优化后带来的效果不是很明显,此外怎么在目前的工程化项目中实践各种优化手段。

一、基本操作

1.1 css、js资源位置

这个点是优化加载。

css 放 header,js 放footer。

目前工程化实践:不用管,webpack 构建自动安排好了。

备注:

1、移动端用于计算 rem 的js文件需要放于header头部,在css之前,避免页面二次布局。

2、css 会阻塞 html 的渲染和 js 的执行,js 会阻塞 html 的解析和渲染,因此 css 是有可能阻塞 html 的解析和渲染的。

1.2 资源混淆压缩

这个点是减少资源大小,标重点,该手段优化效果明显。

混淆压缩资源,一方面是降低代码可读性达到一定的安全作用,一方面是减少资源的大小。

目前工程化实践:不用管,webpack 构建自动安排好了。(手写配置的自己记得配上混淆)

1.3 资源加上 gzip

这个点是减少资源大小,标重点,该手段优化明显,一般能将资源缩小 50% 以上。

需要服务端进行配置,cdn 默认都有该配置,如果资源没上 cdn,则自己在应用服务器上的 nginx 加上 gzip。

目前实践:很多项目为了在线构建或图方便,静态资源都放在应用服务器上,因此这个点也是很多应用没有优化的点

1.4 资源合并

这个点是减少请求数量。

浏览器中对同个域名最多并行6个请求,超出的则进入请求队列等待。减少请求数量可以减少 tcp 握手次数,以及减少网络抖动带来的影响。

目前工程化实践:webpack 默认对 node_modules 中的第三包进行合并打包,合成一个 chunk 文件,因此这块不用管。

这里涉及到一个策略问题,后面将其他相关的优化手段说明完后进行描述。

1.5 图片做雪碧图/iconfont/base64

这个点也是减少请求数量,标重点,有时候该手段能达到很好的优化效果(当一个项目中切图很多的时候)。

这里的图片是项目中用到的切图,雪碧图现在不用管了,基本不这么做了。

iconfont:只针对单色图标,多色图标只能用 svg(多个svg合成一个) 或者 图片做 base64

base64:图片直接做 base64,这个也是目前普遍使用的,一般对 4kb (有的是设置 10kb,根据自己项目情况衡量)以下的图片做 base64。

目前工程化实践:webpack 的 url-loader 做了该功能,配上即可(vue 脚手架都自带着)。

1.6 http 缓存

这个点是在首次访问后二次复用本地资源,减少请求。

http 缓存只建议加在 cdn 上,应用服务器上不要加缓存,带 hash 的可以加

对于打包出来的 hash chunk 资源包,缓存时间可以设置得长一些,比如 30 天、90 天之类的。

其他非 hash chunk 的资源包,本质上都可能存在改内容不改地址的情况,因此缓存禁止设置过长,一般为 1 小时,这样 cdn 更新时,用户的浏览器在 1 小时后也能得到更新。

备注:

1、cdn 缓存非 2 类,一类为节点服务器的缓存,一类为用户浏览器的本地缓存,平时 np 上只是刷新节点服务器的缓存,做不到刷新用户浏览器的缓存。

2、移动端 app 上的 http 缓存只在当前这次 app 存活期间遵循 http 缓存策略,当你的 app 杀死退出后,缓存就会失效。

1.7 压缩图片/使用合适的图片大小

这个点是减少资源大小,标重点,优化效果明显,很多项目都是图片达到几百 kb 甚至 1M 以上。

1)切图图片压缩后再使用,一般用切图的 2 倍图即可,使用 tinypng.com 进行压缩,压缩一遍就好。

2)线上图片。

1.8 图片懒加载

这个点是减少首屏访问的请求数量,标重点,对图片多的页面效果明显,移动端上一屏展示的内容比较少,对列表页的优化效果也明显。

直接用 vue-lazyload 组件就好了。

备注:

图片懒加载和路由懒加载不是一个东西,区分好,不过本质上都是减少首屏加载的资源请求。

1.9 资源放 CDN

资源放 cdn 原本目的是为了让用户请求更快打到服务器上(节点服务器离用户比较近),但现在网速很快,网络节点上的延迟没有以前那么明显,因此该优化手段不是很明显。

放 CDN 另一个目的是上缓存,不过应用服务器衡量好利弊也可以加缓存。

商品管理、学习中心等移动端应用静态资源没有放 CDN 上,也做到了整页加载时间 TP90 在 2 秒内。

1.10 避免重绘重排/减少dom结构复杂度

这个点是优化渲染,效果不明显。

目前浏览器内核很强大了,渲染上的耗时大部分情况下是比请求耗时小不少的。

二、非基本操作

非基本操作是一些新特性的使用,一些细节地方的优化,没有常规手段的效果明显。

2.1 合理利用路由懒加载

这个点只针对前后端分离的前端应用,优化效果看情况,有时候可能形成反效果。

面试的过程中,基本每个面试者我都必问,但没有答得特别好的。

路由懒加载和 1.4 资源合并 有一定的冲突性。

1)资源合并一般针对不经常修改的第三方包,对于业务 JS 代码一般不做合并。

2)路由懒加载是将页面的JS代码拆分成一个一个的 chunk 包,只有加载页面的时候再去加载 JS 资源。

3)用户经常访问的页面,不要用路由懒加载,经常访问的页面主要是首页;对于用户经常访问的页面,因为用户肯定需要将页面的 chunk js 加载下来才可以访问页面,路由懒加载会导致先加载 资源清单 manifest 文件,再去加载页面的 chunk 包,这样就形成了一个资源的串行请求,本来可以一次加载下来却非得拆分成2次而且还是串行的不是并行的。

一个页面的 chunk 包其实不会很大,将它合进主 chunk 里也不会带来明显的请求耗时加大,反而请求次数多了带来的网络延迟消耗比较明显。 image.png

2.2 组件库按需加载

这个点是针对前后端分离的前端应用,优化方向是减少资源大小,标重点,优化效果明显。

一个完整的组件库多大40+个组件,整个包的大小加上 gzip 至少也是 100kb 以上(element-ui 300kb+),但我们项目中常用的组件也就十几二十个,不需要用到那么多组件。

备注:

除了为了让多个系统最高效地复用静态资源缓存,会考虑对第三方包用 CDN 而非 npm 安装,具体情况得多系统衡量,单个系统的优化不明显。

2.3 延迟加载第三方包

这个点是减少首屏加载的资源,但整体页面需要加载的资源没有减少,只是做了分段处理,标重点,优化效果明显。

屏幕能展示的东西就这么多,对于不在屏幕中的内容,且依赖第三方 CDN 包的功能,又或者用户手动触发某个功能依赖第三方 CDN 包(一般页面渲染过程中不需要使用),可以延迟加载这个 CDN 包,没必要在 html 中直接引入,用动态 script 插入使用。

这样可以减少首屏加载渲染的压力。

备注:

要用到的时候才去加载第三方包,就存在第三方包加载过长导致用户觉得卡顿的情况,这是它的劣势,这个问题下面这个优化点解决。

2.4 对资源做 preload 和 prefetch 

这个点是对要用到的资源提前加载好,等要请求的时候直接使用加载好的资源去执行,优化效果看情况。

preload 提前加载当前页面需要用到的资源(不执行),prefetch 提前加载下个页面要用到的资源(不执行)。

这个优化点对优化首屏加载效果看情况,对后续的操作体验比较好。

结合 2.3 延迟加载第三方包 的手段,为了提升用户的交互体验,我们将被我们延迟加载的第三方包使用 preload 进行预加载,这样用户交互的时候,直接执行已经提前加载好的资源即可。

同时对于前后端分离的项目,使用了路由懒加载,这时候加上 prefetch 提前加载下一个页面的资源,可以让用户无感切换页面,不会出现页面延迟出现的情况。

目前工程化实践:vue 脚手架(3.0开始)默认都带着这个功能,自己配的 webpack 配置加上 @vue/preload-webpack-plugin 插件。

手动写法:

<link href=/js/6.719bfbca.js rel=prefetch>
<link href=/css/app.5abb7818.css rel=preload as=style>
<link href=/js/app.d22ec8fc.js rel=preload as=script>

2.5 使用多个图片系统域名

这个点是扩大并发请求,优化效果看情况,对于首页这种商品图很多的页面,效果明显。

前面提到一个域名同时并发 6 个请求,但页面的图片可能不止 6 张,为了让图片更快得呈现,使用多个图片系统域名扩大图片的加载请求数量。

凡是图片系统域名都很多,如 img10.xx.com , img11.xx.com, img20.xx.com, img30.xx.com 等等。

2.6 对域名做 dns 预解析

这个点是减少连接时长,效果在 pc 上一般,在移动上有一定效果,因为移动端的 dns 解析比较差。

前面提到用多个图片系统域名,就带来了多次 dns 解析,有时候页面的加载耗时会在 dns 解析上面卡很久;此外一个页面中至少会请求一个后端接口域名(多的情况2,3个),因此加上一层 dns 预解析是比较好的。

示例:

<link rel="dns-prefetch" href="//img10.xx.com">

将会用到的域名直接加到 html 的 header 中即可,不需要通过 webpack 去生成。

2.7 升级 webpack 版本

这个点本质上也是减少资源大小。

webpack1、webpack2 打包出来的资源包明显大于 webpack3、webpack4。

webpack 的打包机制在优化,用着 webpack1、webpack2 的老项目做 webpack 升级后可以明显减少包的大小。

2.8 小包替大包,手写替小包

这个点本质上也是减少资源大小,优化效果看用到的包的情况。

有些第三方包功能很强大很齐全,但我们只是用到里面的一个小功能而已,将这么大而全的包引入项目中有点得不偿失,可以选择找能实现同样功能的小包或者手撕进行替换。

如 dayjs 替换 momentjs ,momentjs 不做 gzip 的话也接近 100 kb 了。

比如页面用到了 Promise,没必要将整个 babel-polyfill 打进去,装个 es6-promise 就好了。

三、总结

性能优化手段总结下来可以分为以下几个方向:

3.1、减少首屏资源大小

1.2 资源混淆压缩、1.3 资源加上 gzip、1.7 压缩图片/使用合适的图片大小、1.8 图片懒加载、2.1 合理利用路由懒加载、2.2 组件库按需加载、2.3 延迟加载第三方包、2.7 升级webpack 版本、2.8 小包替打包,手写替小包

3.2 减少网络消耗/合理利用网络请求

1.4 资源合并、1.5 图片做雪碧图/iconfont/base64、1.6 http 缓存、1.9 资源放CDN、2.4 对资源做 preload 和 prefetch、2.5 使用多个图片系统域名、2.6 对域名做 dns 预解析

3.3 优化渲染

1.1 css、js 资源位置、1.10 避免重绘重排/减少 dom 结构复杂度

对于首次访问页面来说,3.1 减少首屏资源大小优化效果最明显。

结语

要想页面加载最快,其实就是回归本质,页面简单,没多少资源,不上框架,直接用原生,能用 css 实现的就不要用 js,一个页面中就只有一个 js 文件和 css 文件,这样的页面肯定能达到秒开,但现实情况就是现在的前端应用发展得越来越复杂,只有一些 app 的小页面能做到这么简单,不上框架了。

备注:

天猫那些双十一的技术文章,那么多图片的活动页能实现秒开,主要还是依托于客户端的支持,要么用跨端技术做热更新下发,要么客户端提前加载缓存好活动页面,要用的时候切换过去。

点赞支持、手留余香、与有荣焉,动动你发财的小手哟,感谢各位大佬能留下您的足迹。