本帖最后由 笑尘天雨 于 2022-9-21 00:17 编辑
本帖最后由 笑尘天雨 于 2022-9-21 00:16 编辑
看了大佬的代码,深有所感,于是有了以下代码。
- 之所以不用jQuery,是因为jQuery可以直接传入dom节点将它转成jQuery dom对象。
- 之所以只用document.querySelectorAll, 是因为如果有多个相同的id时,document.querySelectorAll可以都获取到,而jQuery不能。
- 对了,应该加上一个功能,让它在document.readyState为complete时再运行,避免run-at在document-start时导致找不到document.body的问题,不过问题不大,我要睡觉了
- 最后,欢迎大家来挑刺
// ==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);