作为互联网工作者的一员,主要的工作是开发产品、服务客户、制造收益,作为前端来说,主要的工作就是产品开发。一个好的产品,想要长久发展,持续化收益,需要具备可迭代,可持续开发,加载速度快,用户体验好等一系列条件。而这些条件与产品而言,综合的名称就叫性能优化。接下来我们就从产品被访问,被操作等部分讲一讲。
(一)用户访问方面
先从用户访问我们的静态界面来看,我们可以看到用户的访问静态资源的时候依赖了什么来进行访问:
DNS解析
用户进行网页访问,是通过浏览器查询来进行访问的。浏览器加载行为,会接收我们期望访问的链接地址,走`dns`解析然后和远端机器链接。在此期间链接远端服务器,主要依赖就是解析器,解析器一般都是由近到远依次查找,直到查找到远端的服务器连接上后,才会停止查询,开始进行请求类的操作。
产品么,不可能有且只有一次访问,每次的访问必然都要再次进行上述的操作,每次的操作都要进行这么一次流程的话,耗时会非常大,用户等待时长会久,体验性就不好。所以我们需要dns缓存,帮助dns快速访问机器,包括海外的加速性行为,这些都是网络服务商的一些付费内容,具体和公司一起进行商议即可。
浏览器与网络获取
进行完上述操作后,浏览器与服务器连接后,就可以获取页面资源了,网络进行资源获取,会依赖带宽,并发请求数量等方面来获取。- 从带宽方面来说,我们无法决定所有的用户,都使用了很快的网,我们能决定的,仅仅只有文件本身。在同等的带宽下,想要达到文件的快速返回,必须保证文件的大小合理。
-
从并发请求来说,这个是浏览器提供给我们的功能,可以保证页面同时进行多个请求,chrome常用版本都是6个,随着chrome的升级,也有12个和18个的版本,主要看用户是否升级过浏览器。在可并发请求的条件下,我们要保证,用户首屏可见的界面,要最先被请求返回至界面。 从上述的几个条件来看,我们想要做到用户访问的速度加快,需要做到以下几个方面:- 保证文件内容大小合理性
- 我们在开发过程中,要尽量做好组件和方法拆分复用,减少重复代码编写量
- 在webpack打包过程中,利用treeSharking摇走不必要的代码
- 合理利用webpack的plugin和loader,删减或避免复杂代码生成,打包中避免一些多余的操作
- 在package.json层对本地依赖和线上依赖做好区分,避免不必要的依赖被打包
- 利用cdn外链功能,使npm包的依赖不在我们的包内,而在线上加载过程中获取,减少体积
-
合理利用并发请求拆分文件
- 拆分首屏所需代码预先加载(合理使用chunk模式)
- 使用history路由模式
- 图片,字体等文件放在三方存储桶内进行请求
-
Pwa
- 这里是题外话,我们期望用户可以更快的访问我们的界面,可以利用浏览器提供的work service,进行文件存储,在访问界面地址后,查询本地版本和远端版本是否一致,决定是从远端拉取加载还是直接本地获取,以达到秒开效果。
- 这里是题外话,我们期望用户可以更快的访问我们的界面,可以利用浏览器提供的work service,进行文件存储,在访问界面地址后,查询本地版本和远端版本是否一致,决定是从远端拉取加载还是直接本地获取,以达到秒开效果。
-
服务端渲染
-
这个选择性使用,服务端渲染可以将用户渲染行为提前到资源获取部分,但是其中也有一定的缺点存在,这里不做过多的说明,感兴趣的伙伴可以去搜搜SSR。
-
页面渲染
页面渲染部分很简单,就是将我们的html,css,js反馈到视图展示上,不过前端现在很少有写纯html,css,js进行界面绘制的了,都回去依赖框架进行代码编写,所以我们就要从页面渲染方面进行一些优化。
-
合理使用src和href,使页面渲染和资源获取可以同时进行,不在dom渲染过程中阻碍下述的界面。
-
css和js加载合理,一般css放上边,js放下边,页面加载以html和css为主,所以提早加载html和css最合理
-
合理使用GPU,像canvas动画,animation的一些新增动画,是消耗GPU而不是CPU,在动画方面减少CPU消耗会流畅页面渲染,减少页面卡顿等
-
列表类合理分页,加快数据请求速度,减少首屏dom树构建时长
-
对于过多的列表项使用虚拟渲染,过多的dom存在在界面时,页面操作导致的重新渲染会导致界面卡顿,甚至卡死,所以使用虚拟渲染,虽然会导致组件内重新渲染过多,但是减少卡死风险,也是很不错的一种优化方式
-
尽量使用BFC,使界面操作渲染后不会全局刷新而是局部更新DOM
-
减少对元素的js获取操作,元素的获取,元素的像素获取操作等,都会导致dom二次渲染,所以尽量减少非必要的获取行为。
-
减少直接的dom操作行为,入display:none,过多频繁的操作元素的插入和移除也会导致渲染压力啊
-
合理使用懒加载功能,减少非可见页面的资源加载和渲染压力
(二)用户操作
从用户操作行为来看,我们可以看到用的操作后的行为是需要我们在当前界面的事件线程中查找的,所以我们需要针对这个操作行为来做对应的优化:
-
减少事件的绑定,包括eventbus和元素事件
- eventbus的原理其实就是发布订阅模式,你的订阅行为也是存储在操作线程内的,如果你不合理的设置导致页面绑定过多的eventbus是会导致页面不流畅,操作卡顿的情况的,所以要合理设计组件,减少不必要的eventbus绑定
- 合理使用事件委托,当我们在列表类的组件内绑定事件,可以使用dataset记录子元素的必要信息,使用事件委托在父元素绑定对应事件,减少事件线程的事件记录。
-
减少操作中的接口请求(BFF等)
- 我们的一些操作行为是需要和远程的数据库关联的,同一个操作进行多个接口获取和仅使用同一个接口获取的视觉感官上和实际行为上是不一样的,我们使用bff进行接口合并操作,使调用服务端的接口在服务器进行(服务器与服务器之间进行数据请求会快于浏览器且限制少,也是个很不错的优化),并且接口合并操作可以使我们的渲染保持一致,防止某一个渲染操作行为结果返回慢导致的用户体验问题,也可减少每个接口的容错处理
-
做好容错处理
- 我们不仅仅是希望用户在操作上的流畅,也希望用户在操作体验上感受良好,所以必要的容错处理是不可或缺的,做好trycatch,做好sentry集成,做好每次操作后的提示,都是一个好的产品所需要的内容。
-
做好防抖节流
- 这个很常见,避免用户过快的操作或者重复的行为导致界面多次调用同一回调导致的卡顿或响应出错
-
切片处理或使用常连接
- 对于内容量过大的操作行为,和服务端商定进行切片上传,对于常进行的行为操作使用常连接减少和服务端绑定的情况等
(三)框架利用方面
现在的前端开发基本都会使用框架进行页面开发而不是直接性的书写html,js等,所以良好的使用框架也是优化不可或缺的一部分,以下仅使用react进行举例。
- 使用react fiber,react在16.8以后所提供的react fiber对页面的渲染做了很大的优化,这里可以去具体查一查对应的虚拟dom和fiber优化进行了解,对空闲时间利用和BFC的独立视图建立等,都做了很好的利用。
- 减少class组件的使用,现在还有很多的产品要兼容老版本浏览器,所以这里在进行babel编译的时候会采用es5模式,而class本身是es6提出,所以在打包的时候会有工厂模式等一系列操作导致代码量剧增,所以尽量减少class组件使用
- 合理使用memo,这里主要和父子组件更新有关,判别使用情况在于你传输给子组件的props是怎么样的,如果持续性更新就不用包了,有更新有不需要更新的情况下请使用memo
- 合理使用useMemo,useCallback,毕竟框架做界面更新的时候会进行==比较,而复杂数据类型的==比较永远返回为true,会一直认为进行更新,所以这里我们非常(一定)建议,对复杂数据,进行hook包裹,使每次组件更新时,不会重新创建对应变量
- 减少context的使用,或者用hoc做二次封装,这里主要是因为context的传递依赖了provider,类似props的行为,如果你未用到其中某一个值但是你用到了context,就会导致该组件二次更新
- 避免dom原生的获取和操作,框架里使用的都是虚拟dom,对真实dom的操作获取和更新等不会走到虚拟dom的更新回调行为内,也会增加组件或者界面重新渲染的压力,不建议使用。
- 合理使用钩子函数,接口请求,页面操作等请放在componentDidMount之后
- 使用key健,这个主要在列表内需要使用,目的是事件操作的行为内可以准确找到对应更改的元素,减少对其他元素的影响(这里也可以去看看虚拟dom的原理,得以更好的理解key的使用)
期待各位大佬的批评指正
over