上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
12下一页
返回列表 发新帖

[油猴脚本开发指南]Proxy与defineProperty性能分析

[复制链接]

159

主题

1105

帖子

618

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
618
发表于 2021-10-2 22:38:32 | 显示全部楼层 | 阅读模式

前言

先说结论,不要在proxy以及劫持的情况下大量使用同步的强耗时代码,以及不要proxy频繁读取属性的对象。

能用object.definepeoperty的情况下不要使用proxy。

我的测试量应该已经算很大了,延迟其实还是相对个人认为可以接受的。

但是推荐大家依然注意这个问题。以防以后出现卡顿。

因为,你永远不知道写网页的那个脑瘫写的是什么操作

简易测试过程

这里我进行了1000万次的循环,分别为无代理,有代理,代理无Reflect函数,代理无捕获函数,以及Object.defineProperty函数代理。

代码如下

    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[prop];
      },
      set(target, prop, value) {
        return target[prop]=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

观察运行结果可知一千万次正常为90毫秒,如果使用了Object.defineProperty,毫秒数与未代理相差无几。

中间的三个是经过proxy代理的对象。

如果我们不使用任何的捕获函数,也就是notrap,其执行速度与无代理的执行速度等同(这里将在最后被推翻)

如果使用了捕获函数。

使用Reflect的情况下,执行速度比normal慢了4100毫秒左右,而不使用reflec的情况下,可以降低1000毫秒的延时。

然后我们再尝试反复引用对象下的属性,以及进行计算,这里我给出代码。

    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

这里可以看到极大的拉开了差距

原生以及object.defineproperty几乎没有改变。

而proxy即使是无handle的proxy对象,依然会极大的延迟。

这是因为代理引发的,我们对其属性进行读写每次都要经过proxy。

其速度受到了极大的影响。

所以proxy的影响速度与proxy创建的对象的属性的读取次数有极大的关联。

所以我们可以得出一个结论。

能不用proxy尽量不要使用proxy。

proxy可以使用,但是不建议用于频繁读取属性的对象上,如果没有高频读取属性的对象是可以使用的。

影响的性能较少。

补充理论

还有一个问题就是

如果你书写原生js代码,很有可能浏览器对其进行一定程度的优化的,而proxy代理后则很难进行代码计算优化,为了保持计算的正确性。

以上为个人的一点看法,请不要当做实际情况,如果有测试性能差距特别大的情况可以在评论下贴出代码。

大家一起学习分析

结语

撒花~

图片.png
混的人。

159

主题

1105

帖子

618

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
618
发表于 2021-10-2 22:49:10 | 显示全部楼层
@cxxjackie
大佬,这样计算应该没问题吧。。。我有点慌
我看别人计算能得出来defineproperty跟proxy对比,proxy能慢30倍的结论
可能这两年浏览器优化操作了?或者我不严谨?
混的人。
回复

使用道具 举报

8

主题

124

帖子

162

积分

注册会员

Rank: 2

积分
162

活跃会员热心会员突出贡献三好学生猫咪币纪念章中秋纪念章国庆纪念章

发表于 2021-10-2 23:07:47 | 显示全部楼层
李恒道 发表于 2021-10-2 22:49
@cxxjackie
大佬,这样计算应该没问题吧。。。我有点慌
我看别人计算能得出来defineproperty跟proxy对比, ...

Proxy我是真的不太熟悉,也很少用,所以分析不出什么东西来。从你测试的数据来看,确实可以得出Proxy比defineProperty慢的结论吧,我觉得不能想当然的认为一千万次测试就可以将结果除以一千万,万一Proxy的损耗主要在于第一次呢?我是从其调用方式猜测的,也许Proxy的原理是对原对象做了一次深拷贝,将代理操作放到拷贝后的对象上进行,这样的话只有第一次深拷贝会比较耗时(可能原对象越复杂,耗时越多),之后都是在现成的对象上进行了,应该跟直接操作原对象的效率差不多。当然这些都是猜测,你可以再验证一下。
回复

使用道具 举报

159

主题

1105

帖子

618

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
618
发表于 2021-10-2 23:13:48 | 显示全部楼层
cxxjackie 发表于 2021-10-2 23:07
Proxy我是真的不太熟悉,也很少用,所以分析不出什么东西来。从你测试的数据来看,确实可以得出Proxy比de ...

对proxy进行操作的内容也相当于操作原对象的。
感觉应该不太会涉及深拷贝的问题。
问题也不会调试原生代码。
等我有空再研究看看

我测试的数据来看延迟相差实在太低了。
跟别人测试相差30倍速度简直两个结论,卧槽
混的人。
回复

使用道具 举报

159

主题

1105

帖子

618

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
618
发表于 2021-10-2 23:16:33 | 显示全部楼层
cxxjackie 发表于 2021-10-2 23:07
Proxy我是真的不太熟悉,也很少用,所以分析不出什么东西来。从你测试的数据来看,确实可以得出Proxy比de ...

测试了一下,换不同的循环次数得出延迟基本是等比的
混的人。
回复

使用道具 举报

8

主题

124

帖子

162

积分

注册会员

Rank: 2

积分
162

活跃会员热心会员突出贡献三好学生猫咪币纪念章中秋纪念章国庆纪念章

发表于 2021-10-2 23:19:13 | 显示全部楼层
李恒道 发表于 2021-10-2 23:16
测试了一下,换不同的循环次数得出延迟基本是等比的

要不试试把对象弄复杂点,劫持操作也多弄几步?
回复

使用道具 举报

159

主题

1105

帖子

618

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
618
发表于 2021-10-2 23:28:37 | 显示全部楼层
cxxjackie 发表于 2021-10-2 23:19
要不试试把对象弄复杂点,劫持操作也多弄几步?

大佬牛逼!破案了!
混的人。
回复

使用道具 举报

8

主题

124

帖子

162

积分

注册会员

Rank: 2

积分
162

活跃会员热心会员突出贡献三好学生猫咪币纪念章中秋纪念章国庆纪念章

发表于 2021-10-3 00:17:13 | 显示全部楼层
结果上应该可以说明在xhr/fetch上使用Proxy是不太合适的(至少在脚本里不太合适),请求发生的次数较多,很多页面还会自己用一些库去封装xhr,在此基础上用Proxy造成的性能损耗难以估计,definePeoperty还是有其优势所在的。
回复

使用道具 举报

159

主题

1105

帖子

618

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
618
发表于 2021-10-3 00:47:56 | 显示全部楼层
cxxjackie 发表于 2021-10-3 00:17
结果上应该可以说明在xhr/fetch上使用Proxy是不太合适的(至少在脚本里不太合适),请求发生的次数较多,很 ...

我跟大佬意见感觉有点相勃
我觉得fetch不适合用proxy,因为有些网站会在其上面进行属性的读写一堆乱七八糟的东西。
而每个xhr通常只设置了几个属性,读写几次就不会再碰属性了,即使包装了主要的性能消耗感觉可能微乎其微?
如果不是特别频繁,属性的读写消耗可能跟promise的消耗差不多感觉
可能每个xhr就慢0.0几s


我下次再做这个的时候统计一下属性读写次数来观察看看,哥哥晚安
主要fetch劫持可以简单的搞定
xhr的返回内容劫持除了proxy我也想不到什么太好的办法了...

混的人。
回复

使用道具 举报

8

主题

124

帖子

162

积分

注册会员

Rank: 2

积分
162

活跃会员热心会员突出贡献三好学生猫咪币纪念章中秋纪念章国庆纪念章

发表于 2021-10-3 10:00:35 | 显示全部楼层
李恒道 发表于 2021-10-3 00:47
我跟大佬意见感觉有点相勃
我觉得fetch不适合用proxy,因为有些网站会在其上面进行属性的读写一堆乱七八糟 ...

xhr也可以用defineProperty的,劫持open就行了,我之前应该发过:
  1. const xhrOpen = XMLHttpRequest.prototype.open;
  2. XMLHttpRequest.prototype.open = function() {
  3.   const xhr = this;
  4.   if (arguments[1] == hookUrl) {
  5.     const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'response');
  6.     Object.defineProperty(xhr, 'response', {
  7.       get: () => {
  8.         let result = getter.call(xhr);
  9.         //这里可以修改result
  10.         return result;
  11.       }
  12.     });
  13.   }
  14.   return xhrOpen.apply(xhr, arguments);
  15. };
复制代码

之前用__lookupGetter__有兼容性问题,改成Object.getOwnPropertyDescriptor就好了。
回复

使用道具 举报

发表回复

本版积分规则

快速回复 返回顶部 返回列表