笑尘天雨 发表于 2022-9-24 15:00:36

简易版异步获取元素

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

1. querySelectorAll返回的一定是一个数组而不是伪数组
2.


```

// ==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__?.) {
            return targetNode.__observer_use__.Promise;
      } else {
            targetNode.__observer_list__.push(selectorString);
            targetNode.__observer_use__ = createPromise();
            return targetNode.__observer_use__.Promise;
      }
    } else {
      targetNode.__observer_is__ = true;
    }
    targetNode.__observer_list__.push(selectorString);
    targetNode.__observer_use__ = 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__.resolve([...target]);
                  delete targetNode.__observer_use__;
                  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__?..reject([]);
            });
            done();
      }
    }, step)
    return targetNode.__observer_use__.Promise;
}

// code
function querySelector(){
    return querySelectorAll(...arguments).then( res => res);
};
// 测试网站 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("弹幕", 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)
})

```

xinshou271828 发表于 2022-10-12 21:21:53

GGNB学习了
页: [1]
查看完整版本: 简易版异步获取元素