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

异步获取元素的脚本库 ElementGetter

  [复制链接]
  • TA的每日心情

    2023-9-1 16:15
  • 签到天数: 71 天

    [LV.6]常住居民II

    4

    主题

    48

    回帖

    59

    积分

    初级工程师

    积分
    59
    发表于 2022-9-4 17:34:18 | 显示全部楼层
    大佬大佬,诶嘿嘿~我也因为要把脚本传到greasyfork上,所以把您的库的1.2.0传到上面了
    没加license MIT
    https://greasyfork.org/zh-CN/scripts/450726-elementgetter1-2-0

    ↓这是我写的,多谢大佬这一个月来的帮助!要不您看看哪里有可以优化的地方?Q3Q
    https://greasyfork.org/zh-CN/scripts/450728-dulst-com%E7%9A%84cardfighit-vanguard-overdulst%E6%96%87%E6%9C%AC%E6%9B%BF%E6%8D%A2%E6%B1%89%E5%8C%96
    回复
    订阅

    使用道具 举报

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

    [LV.1]初来乍到

    22

    主题

    862

    回帖

    1361

    积分

    荣誉开发者

    积分
    1361

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

    发表于 2022-9-4 20:29:09 | 显示全部楼层
    40252492 发表于 2022-9-4 17:34
    大佬大佬,诶嘿嘿~我也因为要把脚本传到greasyfork上,所以把您的库的1.2.0传到上面了
    没加license MIT
    htt ...

    应该没什么问题吧,主要我不玩这游戏,还得看你的用户有什么反馈。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-3-13 10:14
  • 签到天数: 211 天

    [LV.7]常住居民III

    293

    主题

    3903

    回帖

    3822

    积分

    管理员

    积分
    3822

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

    发表于 2022-9-13 01:49:55 | 显示全部楼层
    帮大佬迁移到油猴脚本的主题了

    https://scriptcat.org/lib/513/latest/ElementGetter.js 的话是最新的版本
    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。/ 微信公众号:一之哥哥
    回复

    使用道具 举报

  • TA的每日心情
    无聊
    2022-8-21 01:21
  • 签到天数: 1 天

    [LV.1]初来乍到

    7

    主题

    58

    回帖

    63

    积分

    初级工程师

    积分
    63
    发表于 2022-9-21 00:10:15 | 显示全部楼层

    本帖最后由 笑尘天雨 于 2022-9-21 00:17 编辑

    本帖最后由 笑尘天雨 于 2022-9-21 00:16 编辑

    看了大佬的代码,深有所感,于是有了以下代码。

    1. 之所以不用jQuery,是因为jQuery可以直接传入dom节点将它转成jQuery dom对象。
    2. 之所以只用document.querySelectorAll, 是因为如果有多个相同的id时,document.querySelectorAll可以都获取到,而jQuery不能。
    3. 对了,应该加上一个功能,让它在document.readyState为complete时再运行,避免run-at在document-start时导致找不到document.body的问题,不过问题不大,我要睡觉了
    4. 最后,欢迎大家来挑刺
    // ==UserScript==
    // @name              async querySelector
    // @namespace         heiheihei
    // @version           1.0.0
    // @author            lll
    // @description       测试
    // @run-at            document-end
    // @match             https://www.bilibili.com/video/*
    // @noframes
    // ==/UserScript==
    
    async function querySelector(selectorString, targetNode = document.body, timerout = 10 * 1000) {
        if ( typeof selectorString !== "string" || targetNode.toString() !== "[object HTMLBodyElement]") {
            return Promise.reject("请输入正确的字符串或正确的dom对象");
        }
        // 观察器的配置(需要观察什么变动)
        const config = { /** attributes: true, */ childList: true, subtree: true };
        /** 在dom上添加自定义字段 */
        // 初始化字段 __use_observer__ 记录当前selectorString是否已经被监听
        if (!targetNode.__use_observer__) {
            targetNode.__use_observer__ = {};
        }
        // 初始化字段 __observer_list__ 记录当前targetNode监听时所需要获取到的dom节点
        if (!targetNode.__observer_list__) {
            targetNode.__observer_list__ = [];
        }
        /** __is_observer__ 为true时,表示已经在监听targetNode了 */
        if (targetNode.__is_observer__ === true) {
            if (targetNode.__use_observer__?.[selectorString]) {
                return targetNode.__use_observer__[selectorString];
            } else {
                targetNode.__observer_list__.push(selectorString);
                return (targetNode.__use_observer__[selectorString] = Promise);
            }
        } else {
            targetNode.__is_observer__ = true;
        }
        targetNode.__observer_list__.push(selectorString);
        // callback 防抖,并作为超时基准值
        targetNode.__observer_timer__ = Date.now() + 15; // 15ms
        let timer;
        // 创建一个观察器实例并传入回调函数
        return (targetNode.__use_observer__[selectorString] = new Promise((resolve, reject) => {
                const observer = new MutationObserver(function fn(mutationsList) {
                    if (Date.now() > targetNode.__observer_timer__) {
                        if (Date.now() > timerout + targetNode.__observer_timer__) {
                            // 超时,所有promise都reject且清除数据并关闭监听
                            targetNode.__observer_list__.forEach((str) => {
                                const rejectMsg = new Error("获取超时");
                                if (str === selectorString) {
                                    return reject(rejectMsg);;
                                }else{
                                    targetNode.__use_observer__[str].reject(rejectMsg);
                                }
                            });
                            clearObserve();
                        }
                        // 过滤数据并查询列表内的每一项
                        targetNode.__observer_list__ = targetNode.__observer_list__.filter((str) => {
                                const target = document.querySelectorAll(str);
                                if (target && target.length) {
                                    if (str === selectorString) {
                                        resolve(target);
                                    } else {
                                        targetNode.__use_observer__[str].resolve( target );
                                    }
                                    delete targetNode.__use_observer__[str];
                                    return false;
                                } else {
                                    return true;
                                }
                            });
                        if (!targetNode.__observer_list__.length) {
                            // 全部完成时,清除数据并关闭监听
                            clearObserve();
                        }
                    } else {
                        clearTimeout(timer);
                        timer = setTimeout(() => fn(mutationsList), 15);
                    }
                });
                // 以上述配置开始观察目标节点
                observer.observe(targetNode, config);
                function clearObserve() {
                    delete targetNode.__use_observer__;
                    delete targetNode.__observer_list__;
                    delete targetNode.__is_observer__;
                    delete targetNode.__observer_timer__;
                    observer.disconnect();
                }
            }
        ));
    }
    // 无错误处理
    // const [target] = await querySelector('[class="bui-danmaku-switch-input"');
    // 对错误进行处理
    // const [target] = await querySelector('[class="bui-danmaku-switch-input"').catch( err => []);
    // 对错误进行处理并设置默认值
    const [target = document.body] = await querySelector('[class="bui-danmaku-switch-input"').catch((err) => [console.log(err)]);
    
    console.log("弹幕", target);
    回复

    使用道具 举报

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

    [LV.1]初来乍到

    22

    主题

    862

    回帖

    1361

    积分

    荣誉开发者

    积分
    1361

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

    发表于 2022-9-21 21:21:30 | 显示全部楼层
    笑尘天雨 发表于 2022-9-21 00:10
    [md]> 本帖最后由 笑尘天雨 于 2022-9-21 00:16 编辑

    看了大佬的代码,深有所感,于是有了以下代码。

    支持jquery主要是为了jquery选择器,有些人可能更习惯用这个。
    我不太喜欢往元素上绑属性的做法,如果有多个脚本同时用了这个库,容易引发冲突,用一个WeakMap来做映射可能更好一点。
    监听属性变化的理由是:假设某元素刚创建时有一个loading的类名,加载完成后去除,这种情况如果选择器写':not(.loading)',结果是获取不到的,因为不算节点插入,元素还是那个元素,只是属性变了而已。还有很多类似的情况,比如写属性选择器,元素的属性不一定第一时间符合,之后属性变化了也应当触发一次检查。
    另外async/await的错误处理是个经典问题了,稍有不慎就会写的很难看,我倾向于能用resolve(null)解决的问题,就尽量不reject,当然这只是个人风格。
    回复

    使用道具 举报

  • TA的每日心情
    无聊
    2022-8-21 01:21
  • 签到天数: 1 天

    [LV.1]初来乍到

    7

    主题

    58

    回帖

    63

    积分

    初级工程师

    积分
    63
    发表于 2022-9-21 22:26:50 | 显示全部楼层

    cxxjackie 发表于 2022-9-21 21:21

    支持jquery主要是为了jquery选择器,有些人可能更习惯用这个。
    我不太喜欢往元素上绑属性的做法,如果有 ...

    1. jquery选择器的问题,querySelector能用的jQuery也能用,而且原生支持比jquery选择器更合适,当然了,这属于个人倾向问题,问题不大
    2. 元素上绑定属性, 我是在这个元素上绑定了一个所需要获取的字段列表,所以真的有不同脚本引用相同的库,这个库上可以共享这个列表一起使用,当然了,想法很不错,至于有没有起效,我还没测试,有空来可以测下看看。
    3. 监听属性变化,我是用了setTimeout延迟执行而且直接使用的原生方法获取没有使用mutations列表,所以不存在你这个问题,当然了坏处就是会有延迟不能第一时间做处理,而且后台执行的时候setTimeout可能还会出问题导致它更慢,不过考虑到大部分场景都是在前台打开页面的,而且设置的15ms延迟,对我个人而言,我还是能接受的
    4. async/await的错误处理,这个说的确实对,所以我将它改成和原生的保持一致,获取不到就返回一个空数组,获取到了转成数组之后再返回
    回复

    使用道具 举报

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

    [LV.1]初来乍到

    22

    主题

    862

    回帖

    1361

    积分

    荣誉开发者

    积分
    1361

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

    发表于 2022-9-21 23:24:44 | 显示全部楼层
    笑尘天雨 发表于 2022-9-21 22:26
    [md]1. jquery选择器的问题,querySelector能用的jQuery也能用,而且原生支持比jquery选择器更合适,当然 ...

    1. jquery有一部分选择器是原生不支持的,可以参考这里:
    https://www.w3school.com.cn/jquery/jquery_ref_selectors.asp
    我这个库的意思是,传入jquery引用就用jquery选择器,不传就用原生的。主要还是之前那个waitForKeyElement的库必须用jquery,这里提供一个改动较小的替换方案。
    2. 我没细究代码,你这个不同线程里resolve不会有问题吗?
    3. 这个不是延迟的问题,而是不监听属性变化,MutationObserver就不会触发,除非之后“恰好”又发生了新的节点插入,使得你有机会进行检查,否则回调函数可能永远都无法触发。
    回复

    使用道具 举报

  • TA的每日心情
    无聊
    2022-8-21 01:21
  • 签到天数: 1 天

    [LV.1]初来乍到

    7

    主题

    58

    回帖

    63

    积分

    初级工程师

    积分
    63
    发表于 2022-9-22 23:53:42 | 显示全部楼层
    cxxjackie 发表于 2022-9-21 23:24
    1. jquery有一部分选择器是原生不支持的,可以参考这里:
    https://www.w3school.com.cn/jquery/jquery_re ...

    1. jQuery属于个人倾向问题,这个倒是没有关系,毕竟使用方式完全不一样,也就没必要考虑兼容以前的一些事。
    2. 这个是最关键的,能不能成全看它了,但是真没时间去试,现在才下班到家。
    3.说实话,我对这几个参数是不了解的,只是感觉它像是监听目标元素的attr才干掉它的。因为考虑到既然只是监听目标元素的attr,而我们正常用的时候肯定是获取目标内部元素的,所以你说的这种情况本身就不存在。当然也有可能是我感觉出问题了 这也很正常。如果attr是监听目标元素和目标内部所有元素的attr,那么就确实有必要了
    回复

    使用道具 举报

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

    [LV.1]初来乍到

    22

    主题

    862

    回帖

    1361

    积分

    荣誉开发者

    积分
    1361

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

    发表于 2022-9-23 21:10:51 | 显示全部楼层
    笑尘天雨 发表于 2022-9-22 23:53
    1. jQuery属于个人倾向问题,这个倒是没有关系,毕竟使用方式完全不一样,也就没必要考虑兼容以前的一些 ...

    attributes 是否观察属性变化
    childList 是否观察节点变动
    subtree 是否应用到所有后代节点
    attributes和subtree组合就可以观察到后代的属性变化。
    回复

    使用道具 举报

  • TA的每日心情
    无聊
    2022-8-21 01:21
  • 签到天数: 1 天

    [LV.1]初来乍到

    7

    主题

    58

    回帖

    63

    积分

    初级工程师

    积分
    63
    发表于 2022-9-23 23:41:45 | 显示全部楼层
    cxxjackie 发表于 2022-9-23 21:10
    attributes 是否观察属性变化
    childList 是否观察节点变动
    subtree 是否应用到所有后代节点

    原来如此,感谢解答。
    那你写的监听document.body的话,岂不是会很耗性能?
    回复

    使用道具 举报

    发表回复

    本版积分规则

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