前言
本文是在第20节课时,由cxxjackie编写的延迟查询代码的解读,意在带大家学习如何阅读他人代码,以及养成一个良好的学习习惯。
为一个加强篇,如果学有余力可以尝试学习。目前没有能力可以跳过。
代码地址https://bbs.tampermonkey.net.cn/thread-835-1-1.html文章下方
解读
代码如下
function getElement(parent, selector, timeout = 0) {
return new Promise(resolve => {
let result = parent.querySelector(selector);
if (result) return resolve(result);
let timer;
const mutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver;
if (mutationObserver) {
const observer = new mutationObserver(mutations => {
for (let mutation of mutations) {
for (let addedNode of mutation.addedNodes) {
if (addedNode instanceof Element) {
result = addedNode.matches(selector) ? addedNode : addedNode.querySelector(selector);
if (result) {
observer.disconnect();
timer && clearTimeout(timer);
return resolve(result);
}
}
}
}
});
observer.observe(parent, {
childList: true,
subtree: true
});
if (timeout > 0) {
timer = setTimeout(() => {
observer.disconnect();
return resolve(null);
}, timeout);
}
} else {
const listener = e => {
if (e.target instanceof Element) {
result = e.target.matches(selector) ? e.target : e.target.querySelector(selector);
if (result) {
parent.removeEventListener('DOMNodeInserted', listener, true);
timer && clearTimeout(timer);
return resolve(result);
}
}
};
parent.addEventListener('DOMNodeInserted', listener, true);
if (timeout > 0) {
timer = setTimeout(() => {
parent.removeEventListener('DOMNodeInserted', listener, true);
return resolve(null);
}, timeout);
}
}
});
}
调用这个函数传入了父元素、匹配的css内容,超时时间
我们可以看到return new Promise,证明了这是一个返回了promise包装的函数。
只有调用resolve返回的promise才会完成执行
let result = parent.querySelector(selector);
if (result) return resolve(result);
首先进行了一次查询,如果存在则返回内容。
const mutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver;
这里是为了对不同浏览器进行兼容性处理,并返回一个可用的函数。
if (mutationObserver)
判断是否存在,如果存在则使用MutationObserver函数,不存在则使用addEventListener函数。
我们先看存在的条件下是如何运行的。
const observer = new mutationObserver(mutations => {
for (let mutation of mutations) {
for (let addedNode of mutation.addedNodes) {
if (addedNode instanceof Element) {
result = addedNode.matches(selector) ? addedNode : addedNode.querySelector(selector);
if (result) {
observer.disconnect();
timer && clearTimeout(timer);
return resolve(result);
}
}
}
}
});
这里声明了一个观察器对象,并传入了一个函数,之前我们已经学过mutations是一个MutationRecord数组
这里对数组进行遍历
然后对每个MutationRecord下的addedNodes数组进行遍历,addedNodes是在监控的对象下插入的对象的数组
判断单个元素是否为元素类型。
然后进行addedNode.matches(selector)。
这里的matches是一个函数,用来进行选择器匹配,类似#test这样的css选择器匹配。
如果为真则返回该元素,如果不为真则进行querySelector对其进行搜寻。
然后返回结果
对结果进行判断,如果结果为真,则取消监听器并判断是否存在超时,如果存在超时则取消定时器循环
然后返回结果
observer.observe(parent, {
childList: true,
subtree: true
});
这里类似于Mutation Events的Dom插入事件。
if (timeout > 0) {
timer = setTimeout(() => {
observer.disconnect();
return resolve(null);
}, timeout);
}
这里判断是否存在超时,如果存在则启动settimeout定时器,在达到事件后无论是否找到元素
都结束操作并返回null。
那么到这里我们已经学习了MutationObserver函数存在的情况了,接下来我们看另一种不存在的情况。
const listener = e => {
if (e.target instanceof Element) {
result = e.target.matches(selector) ? e.target : e.target.querySelector(selector);
if (result) {
parent.removeEventListener('DOMNodeInserted', listener, true);
timer && clearTimeout(timer);
return resolve(result);
}
}
};
声明了一个函数用于待会的DomNodeinserted函数的监听。
这里通过e.target获取到原对象元素。
判断是否为元素类型。
然后则进行与之前相似的判断。
并判断结果,结束EventListerner的监听,并根据情况清空定时器,然后返回结果。
parent.addEventListener('DOMNodeInserted', listener, true);
在元素下监听DOMNodeInserted事件的触发。
if (timeout > 0) {
timer = setTimeout(() => {
parent.removeEventListener('DOMNodeInserted', listener, true);
return resolve(null);
}, timeout);
}
这里已经是一个超时的判断,我们之前已经解释过了。
如何调用
这是两个调用实例,一个是异步的promise+then,一个是await堵塞至彻底返回结果
function example1() {
getElement(document, '#test').then(element => {
//...
});
}
async function example2() {
const element = await getElement(document, '#test');
//...
}
matches兼容性问题
matches存在兼容性问题,如果浏览器不支持则在代码开头加上
Element.prototype.matches = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.webkitMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector;
这个问题可以参考https://developer.mozilla.org/zh-CN/docs/Web/API/Element/matches
补充问题
matches的意义在于检查自身,因为queryselector检查的是子级,无法检测自身是否是匹配元素。