李恒道 发表于 2021-10-1 21:53:30

[油猴脚本开发指南]延迟查询元素代码解读

# 前言

本文是在第20节课时,由cxxjackie编写的延迟查询代码的解读,意在带大家学习如何阅读他人代码,以及养成一个良好的学习习惯。

为一个加强篇,如果学有余力可以尝试学习。目前没有能力可以跳过。

代码地址https://bbs.tampermonkey.net.cn/thread-835-1-1.html文章下方

# 解读

代码如下

```javascript
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才会完成执行

```javascript
    let result = parent.querySelector(selector);
    if (result) return resolve(result);
```

首先进行了一次查询,如果存在则返回内容。

```javascript
const mutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver;
```

这里是为了对不同浏览器进行兼容性处理,并返回一个可用的函数。

if (mutationObserver)

判断是否存在,如果存在则使用MutationObserver函数,不存在则使用addEventListener函数。

我们先看存在的条件下是如何运行的。

```javascript
      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对其进行搜寻。

然后返回结果

对结果进行判断,如果结果为真,则取消监听器并判断是否存在超时,如果存在超时则取消定时器循环

然后返回结果

```javascript
      observer.observe(parent, {
      childList: true,
      subtree: true
      });
```

这里类似于Mutation Events的Dom插入事件。

```javascript
      if (timeout > 0) {
      timer = setTimeout(() => {
          observer.disconnect();
          return resolve(null);
      }, timeout);
      }
```

这里判断是否存在超时,如果存在则启动settimeout定时器,在达到事件后无论是否找到元素

都结束操作并返回null。

那么到这里我们已经学习了MutationObserver函数存在的情况了,接下来我们看另一种不存在的情况。

```javascript
      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的监听,并根据情况清空定时器,然后返回结果。

```javascript
parent.addEventListener('DOMNodeInserted', listener, true);
```

在元素下监听DOMNodeInserted事件的触发。

```javascript
      if (timeout > 0) {
      timer = setTimeout(() => {
          parent.removeEventListener('DOMNodeInserted', listener, true);
          return resolve(null);
      }, timeout);
      }
```

这里已经是一个超时的判断,我们之前已经解释过了。

# 如何调用

这是两个调用实例,一个是异步的promise+then,一个是await堵塞至彻底返回结果

```javascript
function example1() {
getElement(document, '#test').then(element => {
    //...
});
}

async function example2() {
const element = await getElement(document, '#test');
//...
}
```

# matches兼容性问题

matches存在兼容性问题,如果浏览器不支持则在代码开头加上

```javascript
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检查的是子级,无法检测自身是否是匹配元素。

陈公子的话 发表于 2021-10-1 22:21:04

ggnb!!

国家电网0 发表于 2021-10-1 22:25:56

ggnb!!{:4_93:}

脚本体验师001 发表于 2021-10-1 22:33:19

我靠,简直是要爱上你们了,就喜欢这种又硬又能扛饿的代码,一夫当关大杀四方的硬度。大多情况竟都能轻松应对,值得一学

李恒道 发表于 2021-10-1 22:42:37

脚本体验师001 发表于 2021-10-1 22:33
我靠,简直是要爱上你们了,就喜欢这种又硬又能扛饿的代码,一夫当关大杀四方的硬度。大多情况竟都能轻松应 ...

{:4_96:}论坛大家对油猴研究真的贼他妈细...现在知识密集度搞得我都麻了

cxxjackie 发表于 2021-10-1 22:46:33

matches这里我补充一下,因为querySelector不匹配自身,即a.querySelector不会匹配a自己,所以matches就是判断a自己是否符合选择器。

脚本体验师001 发表于 2021-10-1 22:50:55

李恒道 发表于 2021-10-1 22:42
论坛大家对油猴研究真的贼他妈细...现在知识密集度搞得我都麻了

呵呵,这才是追求知识的范儿,掐指一算,这个论坛是要火的一塌糊涂的最后

脚本体验师001 发表于 2021-10-1 22:53:10

cxxjackie 发表于 2021-10-1 22:46
matches这里我补充一下,因为querySelector不匹配自身,即a.querySelector不会匹配a自己,所以matches就是 ...

你确定是个大佬,算过了

李恒道 发表于 2021-10-1 23:37:25

cxxjackie 发表于 2021-10-1 22:46
matches这里我补充一下,因为querySelector不匹配自身,即a.querySelector不会匹配a自己,所以matches就是 ...

这个没考虑到!
页: [1]
查看完整版本: [油猴脚本开发指南]延迟查询元素代码解读