前端的性能优化,说来说去也都是那些事,再发一篇也不过炒炒冷饭,但总归要自己总结一遍才能加深印象。因此这里只提供一个方向,思路的总结,没有具体的操作。
从输入url到页面加载完成发生了什么
从前端的角度,可以大致分为以下几个步骤:
- 用户输入网站地址,如www.baidu.com
- 浏览器通过DNS服务,将域名解析成IP地址
- 浏览器和目的IP建立TCP链接,发送HTTP请求
- 服务器接收并处理请求,最终返回HTTP响应报文
- 浏览器接收到首屏html,开始渲染
- 浏览器将html文档解析成DOM树
- 解析css,生成css规则树
- 将DOM树和css树合并成render树,并进行布局和绘制
从上面步骤可以看出,影响性能的主要是加载和渲染。此外还有运行时的性能:脚本执行。
加载优化
资源加载(网络层面)方面的性能优化主要是减小资源体积,减少请求数量。
减小资源体积
减小资源体积最有效的方法就是压缩。比如js混淆压缩,html压缩,图片压缩等。再加上 Gzip 神器,再压缩三分之一以上不是问题。
你会发现,实际中体积大的基本上是图片,字体这些资源,代码体积反而没那么大。有时候一不小心就把一张 1M 的图片搞到线上去了,比所有代码加起来还大。此外还要特别注意图片格式,png 格式无损压缩,有时候压缩过后还是那么大。可以换成 jpg 格式,略微的质量下降一般看不出来。我的经验是,小一点的图可以用png;背景图,banner 等可以用 jpg。
而字体的优化,在一些场景下,可以直接抽出需要的字体,文件立马小很多。
除此之外,还有按需加载,在组件库、babel-polyfill等场景应用比较多。
减少请求数量
减少请求数量,常用的方法就是缓存和合并请求。
效果最顶的就是浏览器缓存。通过 expires 和 Cache-Control 进行强缓存;通过 Last-Modified 和 ETag 进行协商缓存,就能缓存大部分资源文件。基本上就剩文档(html)和接口的请求了。
第二种缓存是CDN。CDN 是一组分布在各个地区的服务器,它们会缓存资源副本。用户发起请求时,哪个服务器近就由其响应请求。一般存放不需要服务端进行计算,且具有访问频率高,承载流量大的资源,如图片,脚本,样式文件等静态资源。
合并请求的方案就比较多了。如针对小图标的CSS sprite、base64、iconfont等;通过webpack等工具将js、css打包成一个文件。但请求不是越少越好,一个大请求的速度不一定快过几个小请求,浏览器能同时发起几个请求,要充分利用这一特点。
减少请求数量还有懒加载和预加载的方法。懒加载可以减少首屏的请求数量,如 import()。预加载则可以提高后续的用户体验,如预解析 dns-prefetch,预拉取 prefetch,图片预加载等。懒加载和预加载并不冲突,可以理解为不是马上需要的内容可以懒加载,即将需要的内容可以在浏览器空闲的时候预先加载。
渲染优化
提到渲染优化就不得不提回流和重绘。批量操作DOM,动画脱离文档流,requestAnimationFrame 这些方法就不多说了,容易忽略的是获取offsetLeft, offsetTop 等值的时候也可能引起回流。由于回流成本太高,浏览器会将回流操作用队列存储,当过一段时间或达到某个阈值时清空队列,执行一次回流。但当获取有些值时,浏览器会提前触发回流,以确保获取的值是正确的。
其次是非阻塞。js 文件放 body 底部;<script> 标签的 async 和 defer 属性也是大家熟悉的。
初次之外,还有针对首屏渲染优化的服务端渲染SSR,优化首屏体验的骨架屏方案,针对长列表优化的虚拟列表等。
js 代码
代码层面的优化可以说道的很多,但实际上对性能影响很小,可以说是积少成多。除非写出天怒人怨的代码。以下举几个例子:
- 保存深层变量
- 使用
===而非==,避免类型转换带来的性能损耗 - 少用
for-in - 使用
if-else、switch时,将最可能出现的情况放到前面 - 当条件大于(等于)3个时,使用
switch。而当要判断的离散值数量很大时,使用对象或数据实现查表法 - 尾递归
- 避免改变词法作用域(如
eval,with) - 减少操作 DOM
- 事件委托
- 删除 DOM 节点时,移除其绑定的事件
- 用 JS 实现动画时,应使用
requestAnimationFrame - 防抖和节流
- web worker
在使用框架时,还有针对框架的优化。如 Vue 中 v-for 循环 key 的绑定,Object.freeze 取消不会变更的数据的监听,切分成更小的组件以缩小更新的范围等。以及 React 中 useMemo,useCallback 等缓存钩子的使用。
总的来说,学会如何排查 js 脚本中的性能问题更为重要。
总结
前端的性能优化也是针对网站加载、运行中的某一步进行优化,因此熟悉网站如何加载和运行也十分重要。只要其原理不变,那么优化的方向也不会变。雅虎军规在今天也并非一无是处。现在讨论的更多是如何在 webpack 等新工具上进行性能优化。
其次是性能优化的结果要通过指标来衡量。凭感觉去判断比原来“快了”是很不靠谱的。这种性能测量工具有很多,如鼎鼎大名的 lighthouse,这里就不多谈了。