跳到主要内容

延迟查询代码范例解读

作者:cxxjackie/李恒道

本文我们主要解读 cxxjackie 的元素查询代码

/*
@params parent 父元素
@params selector css选择器语句
@params timeout 超时时间
*/
function getElement(parent, selector, timeout = 0) {
//返回了一个Promise,只有调用resolve返回的promise才会完成执行
return new Promise((resolve) => {
//首先判断是否已经存在,如果是则直接返回
let result = parent.querySelector(selector);
if (result) return resolve(result);
let timer;
//对不同浏览器进行兼容性处理,并返回一个可用的函数
const mutationObserver =
window.MutationObserver ||
window.WebkitMutationObserver ||
window.MozMutationObserver;
//判断是否存在,如果存在则使用MutationObserver函数
//不存在则使用addEventListener函数
if (mutationObserver) {
//声明了一个观察器对象,并传入了一个函数
//之前我们已经学过mutations是一个MutationRecord数组
const observer = new mutationObserver((mutations) => {
//对MutationRecord数组进行遍历
for (let mutation of mutations) {
//仅搜寻新增的dom元素
for (let addedNode of mutation.addedNodes) {
//判断单个元素是否为元素类型。
if (addedNode instanceof Element) {
//matches函数判断元素是否符合选择器
//如果为真则返回该元素,如果不为真则进行querySelector对其子树进行搜寻
//matches的意义在于检查自身
//因为queryselector检查的是子级,无法检测自身是否是匹配元素。
result = addedNode.matches(selector)
? addedNode
: addedNode.querySelector(selector);
//如果搜寻到就解除监听,删除超时的定时器并返回结果
if (result) {
observer.disconnect();
timer && clearTimeout(timer);
return resolve(result);
}
}
}
}
});
//监听子元素改变,并监听该元素下的所有Dom元素
observer.observe(parent, {
childList: true,
subtree: true,
});
//设置超时并出现超时则清除监听
if (timeout > 0) {
timer = setTimeout(() => {
observer.disconnect();
return resolve(null);
}, timeout);
}
} else {
//声明了一个函数用于DomNodeinserted事件的回调。
const listener = (e) => {
//通过e.target获取到原对象元素,判断是否是元素类型
if (e.target instanceof Element) {
//matches函数判断元素是否符合选择器
//如果为真则返回该元素,如果不为真则进行querySelector对其子树进行搜寻
result = e.target.matches(selector)
? e.target
: e.target.querySelector(selector);
//如果搜寻到就移除addEventListener监听器,删除超时的定时器并返回结果
if (result) {
parent.removeEventListener("DOMNodeInserted", listener, true);
timer && clearTimeout(timer);
return resolve(result);
}
}
};
//parent元素下监听DOMNodeInserted事件的触发
parent.addEventListener("DOMNodeInserted", listener, true);
//超时判断
if (timeout > 0) {
timer = setTimeout(() => {
parent.removeEventListener("DOMNodeInserted", listener, true);
return resolve(null);
}, timeout);
}
}
});
}

如何使用该代码

// 异步的promise+then
function example1() {
getElement(document, "#test").then((element) => {
//...
});
}
// await堵塞至彻底返回结果
async function example2() {
const element = await getElement(document, "#test");
//...
}

matches 兼容性问题

matches 存在兼容性问题,如果浏览器不支持可以尝试在代码开头加下列代码,具体问题参考MDN

Element.prototype.matches =
Element.prototype.matches ||
Element.prototype.matchesSelector ||
Element.prototype.webkitMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.mozMatchesSelector;