Skip to content
On this page

性能优化

测试性能工具

  • Audits,分别从性能、体验、SEO层面给出打分。
  • Performance

JS性能优化(V8引擎)

JS需要引擎才能运行起来,无论是在浏览器还是Node中,这是解释型语言的特点。

而在V8引擎下,引入了TurboFan编译器,会在特定的情况下进行优化,编译出执行效率更高的Machine Code,当然不是必须的,只是为了提高代码的性能,体现了一点编译性语言的特点。

V8引擎的运行流程

V8引擎中,JS代码会先解析为抽象语法树(AST),然后通过解释器或者编译器转化为机器代码

几点注意点:

  • 代码越多,解析越慢,这是我们需要压缩代码的原因之一
  • 对于函数来说,应该尽可能避免声明嵌套函数(类也是函数),这样会造成函数的重复解析。
  • 编码时,应该尽可能保证传入的类型一致,这样子V8可以认为这段代码可以编译为Machine Code,因为类型固定,不需要再执行多余的逻辑判断;如果我们传入的参数类型改变,那么Machine Code会被DeOptimiezdBytecode,这样就有性能上的损耗。

接下来我们测试一下,到底性能优化了多少:

js
// 测试代码
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');

测试结果如下,可以看出,不优化的代码执行时间是优化的近二十倍:

image

另外,编译器还有一个骚操作——Lazy-Compile,当函数没有被执行时,会对函数进行一次预解析,直到函数被执行以后才会被解析编译。对上述代码来说,test函数先会被预解析一次,然后被调用时再被解析;但对于立即调用函数的情况,这个预解析的过程是多余的,我们可以给函数套上括号,去除预解析的阶段。

js
(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。

html
 <link rel="dns-prefetch" href="//yuchengkai.cn">

预加载

在开发中,对于一些资源不需要马上用到,但希望尽早获取,可以使用预加载。

预加载其实是声明式的fetch,强制浏览器请求资源,并且不会阻塞onload事件。

html
<link rel="preload" href="http://example.com" />

预加载可以在一定程度上降低首屏的加载时间,将一些不影响首屏但重要的文件延后加载,唯一的缺点是兼容性不好

预渲染

可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染

html
<link rel="prerender" href="http://example.com" /> 

预渲染虽然可以提高页面的加载速度,但是要确保该页面大概率会被用户在之后打开,否则就是浪费资源

懒执行

将某些逻辑延迟到使用时再计算。

该技术可以用于首屏优化,对于一些耗时逻辑并不需要在首屏就使用的,可以使用懒执行。

一般可以通过定时器或者事件的调用来唤醒。

懒加载

将不关键的资源延后加载。

懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。

对于图片来说,先设置图片标签的 src 属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 src 属性,这样图片就会去下载资源,实现了图片懒加载。

懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。

CDN

CDN的原理是尽可能的在各个地方分布机房缓存数据,这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。

因此,我们可以将静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名

并且对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie,平白消耗流量。

防抖节流

防抖:控制事件的触发

场景:点击按钮发送请求,我们不希望每次点击都会触发,而是用户点击一段时间后不再点击的情况,再发起网络请求。

js
const debounce = (fn, wait = 50)=>{
	let timer;
	return (...args) => {
		if(timer) clearTimeout(timer);
		timer = setTimeout(() => {
			fn.apply(this, args);
		}, wait);
	}
}

节流:控制事情的频率

场景:滚动时间会触发请求,我们不希望滚动过程中都会触发,而是每隔一段时间发起一次。

js
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
)

MIT Licensed | Copyright © 2021 - 2022