本帖最后由 笑尘天雨 于 2022-9-24 15:47 编辑
- 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)
})