性能优化
测试性能工具
Audits
,分别从性能、体验、SEO层面给出打分。Performance
JS性能优化(V8引擎)
JS需要引擎才能运行起来,无论是在浏览器还是Node中,这是解释型语言
的特点。
而在V8引擎
下,引入了TurboFan
编译器,会在特定的情况下进行优化,编译出执行效率更高的Machine Code
,当然不是必须的,只是为了提高代码的性能,体现了一点编译性语言
的特点。
在V8引擎
中,JS代码会先解析为抽象语法树(AST)
,然后通过解释器或者编译器转化为机器代码
。
几点注意点:
- 代码越多,解析越慢,这是我们需要压缩代码的原因之一
- 对于函数来说,应该尽可能避免声明嵌套函数(类也是函数),这样会造成函数的重复解析。
- 编码时,应该尽可能保证传入的类型一致,这样子V8可以认为这段代码可以编译为
Machine Code
,因为类型固定,不需要再执行多余的逻辑判断;如果我们传入的参数类型改变,那么Machine Code
会被DeOptimiezd
为Bytecode
,这样就有性能上的损耗。
接下来我们测试一下,到底性能优化了多少:
// 测试代码
const v8 = require('v8-natives');
const { performance, PerformanceObserver } = require('perf_hooks')
function test(x) {
return x + x;
}
const obs = new PerformanceObserver((list, observer) => {
console.log(list.getEntries())
observer.disconnect()
})
obs.observe({
entryTypes: ['measure'],
buffered: true
})
performance.mark('start')
let number = 100000000
// 不优化代码
// v8.neverOptimizeFunction(test);
while (number--) {
test(1);
}
performance.mark('end');
performance.measure('test', 'start','end');
测试结果如下,可以看出,不优化的代码执行时间是优化的近二十倍:
另外,编译器还有一个骚操作——Lazy-Compile
,当函数没有被执行时,会对函数进行一次预解析
,直到函数被执行以后才会被解析编译。对上述代码来说,test函数先会被预解析一次,然后被调用时再被解析;但对于立即调用函数的情况,这个预解析的过程是多余的,我们可以给函数套上括号
,去除预解析的阶段。
(function test(obj) {
return x + x
})
相关库:optimize-js
图片优化
图片大小的计算
对于一张100100像素的图片来说,有10000个像素点,如果是RGBA存储的话,每个像素有4个通道,每个通道1个字节,所以图片大小大概为39kB(100001*4/1024)
但在实际项目中,一张图片可能不需要使用那么多颜色去显示,我们可以通过减少像素的调色板来缩小图片的大小。
那么优化图片大小的方案有:
- 减少像素点
- 减少每个像素点能够显示的颜色
图片加载优化
- 不使用图片,修饰类图片,使用CSS去替代
- 对于移动端来说,屏幕宽度有限,不需要加载原图
- 使用CDN加载
- 计算出屏幕的宽度,请求对应尺寸的图片
- 小图使用base64编码
- 将多个图表文件整合到一张图片中(雪碧图)
- 使用正确的图片格式
- 对于能显示
WebP
格式的浏览器尽量使用该格式。WebP
格式具有更好的图像数据压缩算法,能带来更小的图片体积,肉眼基本无法区分,缺点是兼容性不好
- 小图使用
PNG
,对于大部分图标,可以使用SVG
替代 - 照片使用
JPEG
- 对于能显示
资源加载技术
DNS预解析
当浏览器从(第三方)服务器请求资源时,必须先将该跨域域名解析为 IP地址,然后浏览器才能发出请求。此过程称为DNS解析
。
DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。
<link rel="dns-prefetch" href="//yuchengkai.cn">
预加载
在开发中,对于一些资源不需要马上用到,但希望尽早获取,可以使用预加载。
预加载其实是声明式的fetch,强制浏览器请求资源,并且不会阻塞onload事件。
<link rel="preload" href="http://example.com" />
预加载可以在一定程度上降低首屏的加载时间,将一些不影响首屏但重要的文件延后加载,唯一的缺点是兼容性不好
。
预渲染
可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染
<link rel="prerender" href="http://example.com" />
预渲染虽然可以提高页面的加载速度,但是要确保该页面大概率会被用户在之后打开,否则就是浪费资源
。
懒执行
将某些逻辑延迟到使用时再计算。
该技术可以用于首屏优化,对于一些耗时逻辑并不需要在首屏就使用的,可以使用懒执行。
一般可以通过定时器
或者事件的调用
来唤醒。
懒加载
将不关键的资源延后加载。
懒加载的原理就是只加载自定义区域
(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。
对于图片来说,先设置图片标签的 src
属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 src
属性,这样图片就会去下载资源,实现了图片懒加载。
懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。
CDN
CDN
的原理是尽可能的在各个地方分布机房缓存数据,这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。
因此,我们可以将静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限
,可以考虑使用多个 CDN 域名
。
并且对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie,平白消耗流量。
防抖节流
防抖:控制事件的触发
场景:点击按钮发送请求,我们不希望每次点击都会触发,而是用户点击一段时间后不再点击的情况,再发起网络请求。
const debounce = (fn, wait = 50)=>{
let timer;
return (...args) => {
if(timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, wait);
}
}
节流:控制事情的频率
场景:滚动时间会触发请求,我们不希望滚动过程中都会触发,而是每隔一段时间发起一次。
const throttle = (fn, wait = 50) => {
let lastTime = 0;
return (...args) => {
let now = +new Date();
if(now - lastTime > wait){
lastTime = now;
fn.apply(this, args);
}
}
}
setInterval(
throttle(() => {
console.log(1)
}, 500),
1
)