对于做任何端上的产品,用户体验永远是第一位。用户体验本身非常的复杂,它是由非常繁杂的因素组成的。性能也是其中非常重要的一个因素,作为一个前端工程师,如何尽可能的提高性能就显得比较重要了。在我们编码、编译、部署、上线等等环节,其实可以做许多的事情让我们Web端的性能更好,为了提供一个更好的用户体验也献上一份力量。
这是一个checklist,在实践和学习过程的一些总结。记得有本书叫《清单革命》,里面写得就是如何使用清单列表,我们的记忆会有遗忘的时候。以下是根据自己的工作实践、学习到的等的一些总结,作为一个清单列表,经常看看也是在提醒自己。也希望对大家有所帮助。

看到了一个非常好的性能优化的总结:https://3perf.com/talks/web-perf-101/#perf-importance-digits
感觉比自己写的好啊,哈哈

代码层面

JS代码:

  • 把比较重的操作异步处理,通过Promise或者放置在Worker中来处理,减少对于主线程的占用
  • 利用requestidlecallback这样的api把不重要的工作放在空闲的帧来操作,减少失帧
  • 利用requestAnimationFrame,来保证一些动画、视觉的操作是在每一帧开始,减少夸帧的延长处理,导致失帧
  • 提高代码的质量,活用一些算法,数据结构,提高代码本身的质量很重要,毕竟所有的一切最后都是落实到代码层面的。
  • [ ] 引入最小可能的三方包,如果只是用到某个库的某个方法,可以考虑自己实现,而不是引一个包。通过按需加载的技术可以实现对于一些库的按需加载。比如使用babel的babel-plugins-import插件,比如你只使用JQuery的一个queryselector方法,那么其实可以自己实现。当然这一点需要权衡自我实现和引入之间的成本问题。

    CSS代码:

  • [ ] 通常浏览器计算css的时候,没有变动会一层层的去计算元素的css,所以减少css的层数,尽可能简洁、简单很重要。极端的情况下,最好是每个元素只有一个css selector,哈哈,不过好像不太可行,开发起来比较痛苦。不过如果有工具可以做到就好了。现在的可以用一些css-in-js的技术,他会给每一个元素的生成一个hash的名字,可以保证名字的唯一性,并且可以减少继承、覆盖,提高效率。比如styled-components等等的库。当然引入其他库本身会带来一些问题,强调一点是:做好调研工作,要多想考虑是否是必须的,工程很多时候是一种权衡。(很多时候最原始的方式反而会时最好的)

  • 不同css会产生完全不同的计算要求,有的会到时布局的变化、有的会产生重绘等等,这些都是性能杀手,可以看看这个网站(https://csstriggers.com/)里面对不同的css规则会产生,如果可能产生相同的效果,那么尽可能使用只触发Composite的CSS规则。比如尽量使用transform、opacity来实现一些动画等等
  • 如果在js中去变更css,那么注意尽量把css的变更合并起来,不然任何一个小改动都可能会触发一个浏览器的渲染流程,得不偿失。
  • [ ] 可以考虑使用分层的方式来提高用户体验,把网页分层不同的layer,在不同layer去做一些动画操作。不过代价是会提高GPU的负担,提高内存的压力。

    框架

  • [ ] 我们大多数时候都是基于某个框架来开发,了解使用中的框架的最佳实践就比较重要的。了解不够那么误用了一些功能可能会带来用户体验的极其不友好,毕竟框架也是人写出来的,了解思想,了解边界,对我们工程师做出良好体验的应用是非常有效的。下面是一些主流前端框架的最佳实践一些文章,常常看看,多多了解框架的最新动态都是有必要的。

其他:

  • 尽量使用H5定义好的标签
  • 某些时候可以使用iframe来保证主页流畅,iframe就像是一个沙盒,里面的js、css的计算都是和主页隔离的。
  • 某些数据,对于一些数据,我们可以使用想cookie,Local Storage, indexDB的Web存储,把数据存在浏览器端。比如你要做定理位置服务,那么可以把用户查的数据存在用户端,那么下次用户使用的时候就可以直接取了,不需要再请求后端了,也可以实现离线化。不过浏览器可以存储的数据大小是有限的,得注意。
  • 减少绘制的区域,尽量保证变更只是在最小的区域发生,这个的话很多的框架都提供这样的功能了。说到底所有的东西都是更改屏幕上像素,越少越好,计算量越少
  • 另外一种可以考虑的是离线化(PWA),通过ServiceWorker 在初次加载的时候,就可以把所有的文件在后台默默的缓存号,可以大幅度的提升性能加载速度,并且为用户提供了一种离线访问的能力。缺点是暂时不是所有的浏览器都是支持的。当然大多数浏览器都支持Http的Cache,所以虽然到不了离线能力,但是在此加载的时间就会大大缩短,别忘了打开http的cache
  • 可以使用后端渲染的方式(nodejs,springmvc等等),可以大幅度的提高首次paint的出现时间,可以极大提高首页加载的速度,让用户可以更早的有一个感知。
  • 在把css,js等文本文件可以通过CDN来分发到用户端,并开启gzip压缩,gzip对于文本类文件的压缩比可以高达70%。
  • 在Http以及Http2的协议下,考虑不同的策略
  • 在传统的Http下,把不同资源打包成一个文件是更好的策略
  • 在Http2下,则打包成一个个小文件,并且尽量保证小文件大小的一致性,那么可能是更好的策略
  • 我们在做减少资源的大小的努力的同时,减少请求量也是挺重要的,发送请求本身就是挺耗费时间的。反正就是最好的优化就是不发送、不获取资源,尽可能减少无必要的。
  • 通常我们使用标准的Http协议,对于一些大公司来说,协议本身就是一个瓶颈,可以通过自创协议,使用UDP而不是TCP,通过应用本身来保证安全传输等等方式来提高性能,这种方式,一般人都是没有能力去做的。不过可以作为一种考虑。比如QUIC协议
  • 对于图片内容,对比图片格式,通常选择大小比较小的。可以通过压缩、裁剪来减少图片的尺寸。这一点也需要考虑业务场景,如果这是一个高清图片网站,就是要让专业人士欣赏的网站,那么可能这些并不要考虑。图片是传输性能的第一杀手。
  • 对于图片的Loading,可以通过开发自有组件,来保证图片加载和出现的平滑性.
  • 对于字体文件,也是个麻烦事儿,只加载用到的字体,对中文字体,切割字体尽量值保留所需要用到的字。当然压缩啦这些事情肯定要做的。所以尽量使用浏览器本地支持的字体性能上是最好的,不过这个要和体验tradeoff了。
  • 对于字体,一般会有粗细,也就是我们css里面 font-weight 这个属性,不同粗细其实是不同的字体文件,那么最好是实现定义好需要的粗细大小,然后针对性的引入,把不需要的都剔除掉,不用🙅引入。

资源处理

除了代码书写阶段我们可以遵循一些规范和技巧来提高代码质量,提高性能,我们可以通用一些工程手段在编译等阶段,减少不必要的消耗,变相提高性能。

  • minify css, js 文件,十分基本,不能忘记!!。现在的打包工具如webpack支持很完善。
  • 利用打包工具的Tree-shaking来减少不必要代码的打包。webpack,rollup, parcel都有比较完善的支持
  • 有目的的把css,js文件分成多个不同的chunk,方式单个chunk过大,导致首页加载过慢。配合使用按需加载的方式,只在需要的时候去加载对应的css,js文件。按需加载也可能带来一些问题,所以可以利用一些Preload的方式,在首页加载的时候把不必要的都不加载,但是根据业务、根据产品的特性,可以在真正的内容出现前Preload文件。
  • 除了上述的按需加载、Preload等,其实还有一种方式,但是这种方式比较难实现,而且前置条件需要一些积累。就是使用机器学习的方式来按需Preload加载,意思是通过手机用户使用我们产品的过程中产生的数据,其实我们是可以知道预测出用户接下来可能会点击哪些链接、按钮等等,那么就可以提前preload这些相关的css,js文件。可以了解下Google的Guessjs。它最重要的是提供了一种思路就是可以用过机器学习的方式来预测用户的使用习惯,从而决定如何加载,加载什么资源以达到加载最必要资源的目的。

其他

  • 活用各种性能检测工具,找出可以优化点来,比如常用的Chrome Dev Tool的Performance页,观察我们JS运行、内存、渲染等等环节的性能。
  • 多试试在模拟在不同的网络环境、不同设备下,我们的Web应用的加载情况,多用用Chrome Dev Tool的Performance页面,改变各种条件看看自己的应用的情况。
  • 记得之前看Dan Abramov的演讲,提到了三种方式Sync, Debounce, Async,这三种方式,永远选择async的方式,为了达到最好的性能,异步是没法绕过的最好的方式,虽然其带来在各种情境下的难度是很高的。
  • 一些数据可以记着,这是多少前人所留下的宝贵经验
    • 保证60帧的优良体验,那么每一帧的处理时间是16ms左右,浏览器大概需要占用6ms时间,所有剩余给我们的时间大概是10ms左右
    • 对于用户的各种交互操作,优先级应该是尽可能最高的,不要大于100ms。超过100ms其实我们人类就能感受到延迟的感觉了
    • 首页加载时间不要超过5秒,事实上是越快越好的,不然用户是没有耐心等的。

总结

上述的checklist各个项,其实性能问题最后归结到底大多数时候是两个方面:1.执行(资源有限)2.传输(速度有限)。上面的各个项其实都是提高资源有限下执行更快以及速度有限的情况下提高传输速度,因为我们的CPU、GPU、存储、带宽、传输都是有限的,所以我们需要注意我们写得每一行代码。需要通过工程、自动化的手段来提高各方面的利用效率。上述列的项都是比较细的实现层面的东西,很多时候我们需要记住的目标就是:用户体验。然后从我们工程师的角度、从技术的角度来实现良好的用户体验。

最后我想说,性能优化是一个无底洞,这么多的checklist项,并且自己的学识经验有限可能还漏了很多,其实任何一个点都需要慢慢打磨,不一定每一项都需要实现,而是要根据业务、产品的特点来慢慢迭代的,有时候,早点做出来,比做一个完美的东西更重要。我觉得还是要有所取舍的,做技术,或者做其他职业也好,很多时候就是一个tradeoff的过程。并且我们要思考站在用户角度越快就一定是越好吗?什么快?我们会发现其实是一个非常模糊的概念,它不仅仅是一个技术问题,更是一个产品、用户心理、行为等等多方面的问题。所以当我们去优化性能、coding的时候,和不同角度的人去交流,去了解业务、产品、用户、公司成本、开发成本等等因素,那么可以有更加针对性的方案。我一直觉得不能技术反被技术误,得永远保持警觉!!