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

简易版异步获取元素

[复制链接]
  • TA的每日心情
    无聊
    2022-8-21 01:21
  • 签到天数: 1 天

    [LV.1]初来乍到

    7

    主题

    58

    回帖

    63

    积分

    初级工程师

    积分
    63
    发表于 2022-9-24 15:00:36 | 显示全部楼层 | 阅读模式

    本帖最后由 笑尘天雨 于 2022-9-24 15:47 编辑

    1. querySelectorAll返回的一定是一个数组而不是伪数组
    
    // ==UserScript==
    // @name              异步获取元素
    // @namespace         heiheihei
    // @version           1.0.0
    // @author            lll
    // @description       测试
    // @run-at            document-start
    // @match             https://www.bilibili.com/video/*
    // @match             https://zyjs.lngbzx.gov.cn/
    // ==/UserScript==
    
    async function querySelectorAll(selectorString, targetNode, timeout = 15 * 1000) {
    
        /** 将Promise内部的resolve和reject暴露出来,方便后续使用 */
        function createPromise(){
            let resolve, reject, p = new Promise( (_resolve, _reject) => {resolve = _resolve; reject = _reject});
            return { Promise: p, resolve, reject };
        }
    
        /** 等待document加载完成 */
        function awaitDocument(targetDocument = document){
            return new Promise((_) => {
                const fn = () => targetDocument.readyState === "complete" && _(fn);
                if (targetDocument.readyState === "complete") return _(fn);
                targetDocument.addEventListener("readystatechange", fn);
            }).then((fn) => targetDocument.removeEventListener("readystatechange", fn));
        }
    
        // 判断是否为iframe
        if( targetNode && targetNode instanceof HTMLIFrameElement ){
            // @ts-ignore 同域名继续
            if(targetNode.contentDocument){
                await awaitDocument(targetNode.contentDocument);
                let ce = targetNode.contentWindow.HTMLElement;
                targetNode = targetNode.contentDocument.body;
                // 开始执行
                if ( !(typeof selectorString === "string" && targetNode instanceof ce)) {
                    console.warn(new Error("请输入正确的字符串或正确的dom对象"));
                    return Promise.reject([]);
                }
            }else{
                console.warn(new Error("无法获取跨域iframe"));
                return Promise.reject([]);
            }
        } else {
            // dom加载完之后再获取
            await awaitDocument(document);
            targetNode = targetNode || document.body;
            // 开始执行
            if ( !(typeof selectorString === "string" && targetNode instanceof HTMLElement)) {
                console.warn(new Error("请输入正确的字符串或正确的dom对象"));
                return Promise.reject([]);
            }
        }
        // 预检查,如果预检查能找到便终止,避免节点无变动时不触发后续流程
        const target = targetNode.querySelectorAll(selectorString);
        if(target && target.length){
            return [...target];
        }
        // 观察器的配置(需要观察什么变动)
        const config = { attributes: true, childList: true, subtree: true };
        /** 在dom上添加自定义字段 */
        // 初始化字段 __observer_use__ 记录当前selectorString是否已经被监听
        if (!targetNode.__observer_use__) {
            targetNode.__observer_use__ = {};
        }
        // 初始化字段 __observer_list__ 记录当前targetNode监听时所需要获取到的dom节点
        if (!targetNode.__observer_list__) {
            targetNode.__observer_list__ = [];
        }
        // callback 防抖,并作为超时基准值 15ms
        targetNode.__observer_timer__ = Date.now() + 15;
        /** __observer_is__ 为true时,表示已经在监听targetNode了 */
        if (targetNode.__observer_is__ === true) {
            if (targetNode.__observer_use__?.[selectorString]) {
                return targetNode.__observer_use__[selectorString].Promise;
            } else {
                targetNode.__observer_list__.push(selectorString);
                targetNode.__observer_use__[selectorString] = createPromise();
                return targetNode.__observer_use__[selectorString].Promise;
            }
        } else {
            targetNode.__observer_is__ = true;
        }
        targetNode.__observer_list__.push(selectorString);
        targetNode.__observer_use__[selectorString] = createPromise();
        // 防抖和超时
        let timer, outerTime;
        // 创建一个观察器实例并传入回调函数
        const observer = new MutationObserver(function fn() {
            if(!targetNode.__observer_timer__) return done();
            if (Date.now() > targetNode.__observer_timer__) {
                targetNode.__observer_timer__ = Date.now() + 15;
                if(!targetNode.__observer_list__?.length) return done();
                // 过滤数据并查询列表内的每一项
                targetNode.__observer_list__ = targetNode.__observer_list__.filter((str) => {
                    const target = targetNode.querySelectorAll(str);
                    if (target && target.length) {
                        targetNode.__observer_use__[str].resolve([...target]);
                        delete targetNode.__observer_use__[str];
                        return false;
                    }
                    return true;
                });
            } else {
                clearTimeout(timer);
                timer = setTimeout(fn, 15);
            }
        });
    
        // 以上述配置开始观察目标节点
        observer.observe(targetNode, config);
        /** 全部完成时,清除数据并关闭监听 */
        function done() {
            delete targetNode.__observer_is__;
            delete targetNode.__observer_use__;
            delete targetNode.__observer_list__;
            delete targetNode.__observer_timer__;
            observer.disconnect();
            clearTimeout(timer);
            clearInterval(outerTime);
        }
        // 处理timeout
        timeout = parseFloat(timeout);
        timeout = isNaN(timeout) ? 10 * 1000 : timeout;
        // 超时和限定范围,暂定1/2为定时间隔,3倍为界定超时避免无限制调用导致无法关闭
        const step = timeout / 2 + 31, deadline = timeout * 3;
        outerTime = setInterval(()=>{
            if(!targetNode.__observer_is__) return done();
            if (Date.now() >= timeout + targetNode.__observer_timer__ || Date.now() >=deadline) {
                // 超时,所有promise都reject且清除数据并关闭监听
                targetNode.__observer_list__?.forEach((str) => {
                    console.warn(new Error("获取超时"));
                    targetNode.__observer_use__?.[str].reject([]);
                });
                done();
            }
        }, step)
        return targetNode.__observer_use__[selectorString].Promise;
    }
    
    // code
    function querySelector(){
        return querySelectorAll(...arguments).then( res => res[0]);
    };
    // 测试网站 bilibili https://www.bilibili.com/video/BV1194y1m7b7
    const base = performance.now();
    querySelector('[class="bui-danmaku-switch-input"').then((res) => {
        console.log('总计用时', performance.now() - base)
        console.log("弹幕[class]", res);
    });
    querySelector('.bui-danmaku-switch-input').then((res) => {
        console.log("总计用时", performance.now() - base);
        console.log("弹幕.bui", res);
    });
    querySelector('.bpx-player-sending-area input.bui-checkbox-input').then((res) => {
        console.log("总计用时", performance.now() - base);
        console.log("弹幕.check", res);
    });
    
    // 测试网站 https://zyjs.lngbzx.gov.cn/
    // 获取iframe内元素
    querySelector('#renewframe').then( targetIframe => {
        console.log("总计用时 iframe", performance.now() - base);
        return querySelector('.kclist1.fl dl:nth-of-type(3)', targetIframe)
    }).then( res => {
        console.log("总计用时", performance.now() - base);
        console.log('我也不知道啥用途的 dl', res)
    })
    
  • TA的每日心情
    开心
    2022-10-12 21:58
  • 签到天数: 2 天

    [LV.1]初来乍到

    0

    主题

    4

    回帖

    8

    积分

    助理工程师

    积分
    8
    发表于 2022-10-12 21:21:53 | 显示全部楼层
    GGNB学习了
    回复

    使用道具 举报

    发表回复

    本版积分规则

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