[油猴脚本开发指南]Proxy与defineProperty性能分析
# 前言先说结论,不要在proxy以及劫持的情况下大量使用同步的强耗时代码,以及不要proxy频繁读取属性的对象。
能用object.definepeoperty的情况下不要使用proxy。
我的测试量应该已经算很大了,延迟其实还是相对个人认为可以接受的。
但是推荐大家依然注意这个问题。以防以后出现卡顿。
因为,你永远不知道写网页的那个脑瘫写的是什么操作
# 简易测试过程
这里我进行了1000万次的循环,分别为无代理,有代理,代理无Reflect函数,代理无捕获函数,以及Object.defineProperty函数代理。
代码如下
```javascript
var target = {
a: 0,
};
fornum = 10000000;
console.time("time_normal");
let result_normal = 0;
for (let index = 0; index < fornum; index++) {
result_normal += target.a;
target.a = index;
}
console.timeEnd("time_normal");
target.a = 0;
let handle = {
get: function (target, prop, receiver) {
return Reflect.get(target, prop);
},
set(target, prop, value) {
return Reflect.set(target, prop, value);
},
};
let targetproxy = new Proxy(target, handle);
result_normal = 0;
console.time("time_proxy");
for (let index = 0; index < fornum; index++) {
result_normal += targetproxy.a;
targetproxy.a = index;
}
console.timeEnd("time_proxy");
target.a = 0;
let handlenoreflect = {
get: function (target, prop, receiver) {
return target;
},
set(target, prop, value) {
return target=value;
},
};
let targetnoreflect = new Proxy(target, handlenoreflect);
result_normal = 0;
console.time("time_noreflect");
for (let index = 0; index < fornum; index++) {
result_normal += targetnoreflect.a;
targetnoreflect.a = index;
}
console.timeEnd("time_noreflect");
target.a = 0;
let handlenotrap = {};
let targetproxynotrap = new Proxy(target, handlenotrap);
result_normal = 0;
console.time("time_proxy_notrap");
for (let index = 0; index < fornum; index++) {
result_normal += handlenotrap.a;
handlenotrap.a = index;
}
console.timeEnd("time_proxy_notrap");
let b = 0;
Object.defineProperty(target, "a", {
get: function () {
return b;
},
set: function (str) {
b = str;
},
});
result_normal = 0;
console.time("time_define");
for (let index = 0; index < fornum; index++) {
result_normal += target.a;
target.a = index;
}
console.timeEnd("time_define");
```
# 运行结果
![图片.png](data/attachment/forum/202110/02/223333jfzfdvk7v8vvfy7v.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")
观察运行结果可知一千万次正常为90毫秒,如果使用了Object.defineProperty,毫秒数与未代理相差无几。
中间的三个是经过proxy代理的对象。
如果我们不使用任何的捕获函数,也就是notrap,其执行速度与无代理的执行速度等同(这里将在最后被推翻)
如果使用了捕获函数。
使用Reflect的情况下,执行速度比normal慢了4100毫秒左右,而不使用reflec的情况下,可以降低1000毫秒的延时。
然后我们再尝试反复引用对象下的属性,以及进行计算,这里我给出代码。
```javascript
for (let index = 0; index < fornum; index++) {
result_normal += targetproxy.a;
targetproxy.a = index;
targetproxy.b.c=targetproxy.a
targetproxy.b.d=targetproxy.b.c+1;
}
```
然后运行进行测试
![图片.png](data/attachment/forum/202110/02/233439sna2iqw9njnmw2s5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")
这里可以看到极大的拉开了差距
原生以及object.defineproperty几乎没有改变。
而proxy即使是无handle的proxy对象,依然会极大的延迟。
这是因为代理引发的,我们对其属性进行读写每次都要经过proxy。
其速度受到了极大的影响。
所以proxy的影响速度与proxy创建的对象的属性的读取次数有极大的关联。
所以我们可以得出一个结论。
能不用proxy尽量不要使用proxy。
proxy可以使用,但是不建议用于频繁读取属性的对象上,如果没有高频读取属性的对象是可以使用的。
影响的性能较少。
# 补充理论
还有一个问题就是
如果你书写原生js代码,很有可能浏览器对其进行一定程度的优化的,而proxy代理后则很难进行代码计算优化,为了保持计算的正确性。
以上为个人的一点看法,请不要当做实际情况,如果有测试性能差距特别大的情况可以在评论下贴出代码。
大家一起学习分析
# 结语
撒花~
@cxxjackie
大佬,这样计算应该没问题吧。。。我有点慌
我看别人计算能得出来defineproperty跟proxy对比,proxy能慢30倍的结论
可能这两年浏览器优化操作了?或者我不严谨? 李恒道 发表于 2021-10-2 22:49
@cxxjackie
大佬,这样计算应该没问题吧。。。我有点慌
我看别人计算能得出来defineproperty跟proxy对比, ...
Proxy我是真的不太熟悉,也很少用,所以分析不出什么东西来。从你测试的数据来看,确实可以得出Proxy比defineProperty慢的结论吧,我觉得不能想当然的认为一千万次测试就可以将结果除以一千万,万一Proxy的损耗主要在于第一次呢?我是从其调用方式猜测的,也许Proxy的原理是对原对象做了一次深拷贝,将代理操作放到拷贝后的对象上进行,这样的话只有第一次深拷贝会比较耗时(可能原对象越复杂,耗时越多),之后都是在现成的对象上进行了,应该跟直接操作原对象的效率差不多。当然这些都是猜测,你可以再验证一下。 cxxjackie 发表于 2021-10-2 23:07
Proxy我是真的不太熟悉,也很少用,所以分析不出什么东西来。从你测试的数据来看,确实可以得出Proxy比de ...
对proxy进行操作的内容也相当于操作原对象的。
感觉应该不太会涉及深拷贝的问题。
问题也不会调试原生代码。
等我有空再研究看看
我测试的数据来看延迟相差实在太低了。
跟别人测试相差30倍速度简直两个结论,卧槽 cxxjackie 发表于 2021-10-2 23:07
Proxy我是真的不太熟悉,也很少用,所以分析不出什么东西来。从你测试的数据来看,确实可以得出Proxy比de ...
测试了一下,换不同的循环次数得出延迟基本是等比的 李恒道 发表于 2021-10-2 23:16
测试了一下,换不同的循环次数得出延迟基本是等比的
要不试试把对象弄复杂点,劫持操作也多弄几步? cxxjackie 发表于 2021-10-2 23:19
要不试试把对象弄复杂点,劫持操作也多弄几步?
大佬牛逼!破案了! 结果上应该可以说明在xhr/fetch上使用Proxy是不太合适的(至少在脚本里不太合适),请求发生的次数较多,很多页面还会自己用一些库去封装xhr,在此基础上用Proxy造成的性能损耗难以估计,definePeoperty还是有其优势所在的。 cxxjackie 发表于 2021-10-3 00:17
结果上应该可以说明在xhr/fetch上使用Proxy是不太合适的(至少在脚本里不太合适),请求发生的次数较多,很 ...
我跟大佬意见感觉有点相勃
我觉得fetch不适合用proxy,因为有些网站会在其上面进行属性的读写一堆乱七八糟的东西。
而每个xhr通常只设置了几个属性,读写几次就不会再碰属性了,即使包装了主要的性能消耗感觉可能微乎其微?
如果不是特别频繁,属性的读写消耗可能跟promise的消耗差不多感觉
可能每个xhr就慢0.0几s
我下次再做这个的时候统计一下属性读写次数来观察看看,哥哥晚安
主要fetch劫持可以简单的搞定
xhr的返回内容劫持除了proxy我也想不到什么太好的办法了...
李恒道 发表于 2021-10-3 00:47
我跟大佬意见感觉有点相勃
我觉得fetch不适合用proxy,因为有些网站会在其上面进行属性的读写一堆乱七八糟 ...
xhr也可以用defineProperty的,劫持open就行了,我之前应该发过:
const xhrOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
const xhr = this;
if (arguments == hookUrl) {
const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'response');
Object.defineProperty(xhr, 'response', {
get: () => {
let result = getter.call(xhr);
//这里可以修改result
return result;
}
});
}
return xhrOpen.apply(xhr, arguments);
};
之前用__lookupGetter__有兼容性问题,改成Object.getOwnPropertyDescriptor就好了。
页:
[1]
2