笔者最近在一个Vue项目里面引入了一个动画库,可是发现性能有点异常,项目里面使用的CPU是在一个demo页面的3.5倍左右,我已经把项目里全部其它干扰的东西都给删掉了,可是CPU就是降不下去,以下图所示,正常范围是在2.1%左右波动:javascript
可是引到项目里面就变成了7%左右波动:html
这个会不会是由于html嵌套太深致使Layout等计算复杂,因此CPU上升了呢,笔者尝试把DOM结构简单化,以及加上contain: strict等Layout隔离的方法,也是没有效果。因此只能是JS执行问题了,经过Chrome devtools的Performance能够研究这个问题。java
以下图所示:react
上面密密麻麻的线都是requestAnimationFrame的回调,把它放大,而后查看一个回调,比较一下demo页面和Vue页面的不一样之处,以下图所示:async
这里明显能够看出区别,demo.html每一个回调的执行时间是0.3ms左右,而Vue项目的回调执行时间达到了0.8ms左右,快接近3倍,且调用栈深了不少。多出来的这些东西是什么呢?仔细一看:函数
这些东西是Vue里面的,也就是Vue里面setter,部分回调里面还包含了Vue里的getter:性能
这个时候恍然大悟,由于Vue里面重写了变量的getter/setter,致使获取某个属性或者改写某个属性的时间变长,致使CPU上升。形成Vue重写的缘由是由于在代码里面把动画库的变量当成了组件里this的属性,以下代码所示:测试
import Player from 'player.js';
export default {
data: {
return {
player: new Player()
};
}
};复制代码
而后Vue就会遍历这个player对象,给全部的属性都加上setter/getter,以下控制打印所示:动画
这里的Ir.set就是上面Performance里面的截图,也就是这个致使了设置Ii变量变慢了。这里咱们注意到一个细节,Chrome控制台会直接打印没有覆盖setter/getter的Object,而设置了的,将会是用“(...)”代替,而后等到你去点的时候再去获取它当前的值显示出来。ui
从Vue源码里面能够看到,Vue会对成员变量进行defineProperty设置setter和getter:
// 代码有所删减
function defineReactive$$1 (obj, key, val) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
// 从源码也能够看到,能够把obj的configurable置为false,Vue便不会设置getter和setter
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
dep.notify();
}
});
}复制代码
以便使用者设置值的时候作一些通知,从而达到数据驱动的目的。但同时也有可能形成性能问题,在这个例子里面是增长了0.3ms左右的调用时间。实际上这个时间几乎是能够忽略的,可是因为这个例子里面须要运行在requestAnimationFrame里面,1s调用60次,比较频繁,本来的时候也就才0.2ms,而如今因为这个setter/getter,增长了0.3ms,比正常时间多了一倍多,因此CPU就升上去了。
知道缘由就能解决问题了,如今的解决方式是不要把这个player变量当成this里面的成员属性,而是把它弄到外面去,以下代码所示:
import Player from 'player.js';
let player = new Player();
export default {
data: {
return {
};
}
};复制代码
(补充:从Vue的源码也能够看到,把object的configurable属性置成false也能够解决问题。)
这个时候CPU从7%降到了4%左右,快接近一半,以下图所示:
查看Performance里面的setter的调用栈就没有了,以下图所示:
可是CPU仍然是demo页面的两倍(2%和4%),这个时候继续查看调用栈,发现是一个ji的函数调用时间一个是另外一个的两倍:
这两个函数点过去Source面板看代码的时候确认是两个同样的函数,这里惟一的区别可能在于demo.html用的是压缩的代码,而本地的项目是未压缩,若是打包压缩一下,放到测试环境,能够看到CPU时间基本就差很少了:
压缩代码里面会把多条语句合并为一条语句应该也会提高点性能。
综上,本文并非说Vue的实现有问题,只是须要注意setter/getter对性能的影响,特别是在一个动画的回调里面,通常状况下对于一次性的操做影响几乎是可忽略的,应该不须要关心这个问题。另外,只是设置动画里面的setter/getter也不必定会使CPU一会儿就升上去了,还要看你在setter/getter里面干了些啥,在Vue里面能够看到它的调用栈是比较深的,可能内部须要判断的东西比较多。
另外这个研究让想起了一个有趣的问题,如何让CPU使用率维持在50%?若是我写一个for写循环,那么CPU使用率必定是100%(它把一个核跑满了),以下代码所示:
let now = Date.now();
// 跑个50s
while (Date.now() - now < 50000);复制代码
这个时候CPU使用率就是100%:
若是我让它睡眠50ms,而后再干50ms,反复交替,以下代码所示:
function sleep (time) {
return new Promise(resolve => {
setTimeout(resolve, time);
});
}
let now = Date.now();
async function start () {
while (Date.now() - now < 50000) {
// 睡50ms
await sleep(50);
let current = Date.now();
// 干50ms
while (Date.now() - current < 50);
}
console.log('end');
}
start();复制代码
这个时候CPU使用率就会在50%左右波动,以下图所示:
是否是挺有趣的呢