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

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

[复制链接]
  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    633

    主题

    5173

    回帖

    6052

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6052

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 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代理后则很难进行代码计算优化,为了保持计算的正确性。

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

    大家一起学习分析

    结语

    撒花~

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    633

    主题

    5173

    回帖

    6052

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6052

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2021-10-2 22:49:10 | 显示全部楼层
    @cxxjackie
    大佬,这样计算应该没问题吧。。。我有点慌
    我看别人计算能得出来defineproperty跟proxy对比,proxy能慢30倍的结论
    可能这两年浏览器优化操作了?或者我不严谨?
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    861

    回帖

    1360

    积分

    荣誉开发者

    积分
    1360

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

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

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

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    633

    主题

    5173

    回帖

    6052

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6052

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

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

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

    我测试的数据来看延迟相差实在太低了。
    跟别人测试相差30倍速度简直两个结论,卧槽
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    633

    主题

    5173

    回帖

    6052

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6052

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

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

    测试了一下,换不同的循环次数得出延迟基本是等比的
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    861

    回帖

    1360

    积分

    荣誉开发者

    积分
    1360

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

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

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

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    633

    主题

    5173

    回帖

    6052

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6052

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

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

    大佬牛逼!破案了!
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    861

    回帖

    1360

    积分

    荣誉开发者

    积分
    1360

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

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

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    633

    主题

    5173

    回帖

    6052

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6052

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 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我也想不到什么太好的办法了...

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    861

    回帖

    1360

    积分

    荣誉开发者

    积分
    1360

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

    发表于 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就好了。
    回复

    使用道具 举报

    发表回复

    本版积分规则

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