web性能测试及优化
为什么web性能重要
怎么测试web性能
怎么改善性能
1 Why does speed matter
这部分我不是很想说,就像在搜集证据说钱多么重要一样,但还是应付的给个参考链接:Why does speed matter?。
比如性能关乎用户体验,进一步影响用户留存、转化率。
2 How to measure speed
评测一个web应用包含很多指标,比如RAIL model对response, animation, idle, and load四个方面进行了评估。
另外推荐一些常用的测试工具,比如Chrome DevTools,WebPageTest,lighthouse。
其中WebPageTest是一个在线的测试网站,可以对指定域名生成测评报告,结果如下
lighthouse可用chrome自带版本,也可以使用lighthouse-cli,结果如下
带有--locale=zh-cn
参数可以指定中文报告,打分指标参考这里。
PageSpeed Insights也使用了lighthouse的数据。
3 How to improve performance
这部分我打算分两个部分介绍
内容加载
内容渲染
3.1 内容加载
这部分介绍页面怎么减少内容加载对网站性能的影响,可以分为以下思路,即一个页面的加载,从请求、传输、下载以及其他细节方面来考虑
3.1.1 减少请求次数
利用ssr,加快首屏渲染,有利于seo
http2的server push,也能加快首屏渲染,但和seo没关
用css样式代替图片或雪碧图或者base64表示图片
代码打包合并
本地存储
3.1.2 减少下载次数
使用http缓存
3.1.3 减少下载总量
压缩资源,包括代码和图片等,包括打包时的压缩和http压缩
少使用第三方library
tree shaking
将部分第三方包由runtime提供,比如webpack的externals,多用于library开发
3.1.4 减少网络传播时间
cdn
http/2
减少header payload
3.1.5 其他
代码拆分
懒加载,比如script的defer或async属性或者按需加载
预处理,link标签的rel属性相关的优化,包括
dns-prefetch 提前解析dns
preconnect 提前建立连接(包括dns查询、tcp连接和tls协商)
prefetch 下次导航时可能需要,应该提前获取
prerender 下个导航可能需要,应该提前执行,比如渲染一个html
preload 相对于prefetch优先级高,且是当前导航中需要的资源
3.2 内容渲染
本部分会结合页面的渲染中的五个步骤,对其进行优化,还会参考Rendering Performance。
大部分设备屏幕刷新率为60fps,这样渲染时的每一帧需要16ms的准备时间,每一帧期间除渲染本身以外的其他工作只有10ms,如果超过这个时间就会掉帧,这被称作 jank。
3.2.1 parse
渲染的第一步本是解析html,这里代指所有会引起页面渲染的操作,比如动画、过渡、scroll或其他交互。
首次渲染,首次渲染时诸多因素与内容加载有关系,注意遇到script会阻塞页面解析,css随不会阻塞,但是页面要等cssom解析完成才能下一步操作,具体参考关键路径优化。
使用window.requestAnimationFrame()可以在帧开始执行画面修改,而不是其他timer,因为后者可能发生在帧的结尾时刻,导致丢帧。
对于长时间的工作,如果是纯计算等可以使用web worker完成的就开一个worker线程,否则对于优先级高的任务可以使用window.requestAnimationFrame(),优先级低的使用window.requestIdleCallback()将复杂计算转换成多个小计算来进行。
少操作dom,dom操作本身就很消耗性能
3.2.2 Style calculation
样式计算分为两个步骤
计算出哪些元素应用当前样式,这一步可以通过降低选择器复杂度来解决,比如使用BEM命名规范,具体选择器解析顺序是从右到左,创建选择器时应加以注意。
将样式匹配到对应的元素上,这一步可以通过减少要计算样式的元素数量解决。
3.2.3 layout
layout是计算各需要显示元素的坐标
选用不影响元素坐标的动画或过渡,包括 transforms or opacity
使用flexBox而不是旧的模型,后者盒子多
避免forced synchronous layout造成的卡顿或layout thrashing。一个页面包含两个tree,其中一个表示实际的dom,一个表示内存中被js操作的dom,操作前者很耗费性能,当读取元素样式时会强制元素渲染,即修改实际dom,在执行一系列dom操作时应避免这种情况,比如
function resizeAllParagraphsToMatchBlockWidth() {
// Puts the browser into a read-write-read-write cycle.
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px';
}
}
复制代码
应修改为
// Read.
var width = box.offsetWidth;
function resizeAllParagraphsToMatchBlockWidth() {
for (var i = 0; i < paragraphs.length; i++) {
// Now write.
paragraphs[i].style.width = width + 'px';
}
}
复制代码
使用DocumentFragment组织连续dom的插入,参考Document Fragments and why you should use them,如果单独插入,每次插入都是一个宏任务,都会引起页面渲染,如果用该方法可以减少渲染次数。
3.2.4 paint
这里计算绘制顺序,layout后就会paint,这一步没有特别的优化步骤
3.2.5 compositing
这一步将前面获取的信息分层并分别栅格化
利用willchange或transform: translateZ(0)将需要改动的元素分到单独的层,主要分层本身就消耗性能,不要对过多元素使用
降低动画复杂度,那些涉及模糊的消耗性能更多,比如
background: red;
和box-shadow: 0, 4px, 4px, rgba(0,0,0,0.5);
相比对于标记为Non-Fast Scrollable Region的区域,即有事件绑定的区域,如果存在交互,会触发main线程处理,可以在事件监听器使用
passive: true
解决,具体参考这里。throttle和debounce