上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

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

[复制链接]

159

主题

1105

帖子

618

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
618
发表于 2021-10-1 21:53:30 | 显示全部楼层 | 阅读模式

前言

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

混的人。

136

主题

416

帖子

446

积分

版主

Rank: 7Rank: 7Rank: 7

积分
446

猫咪币纪念章三好学生活跃会员热心会员突出贡献中秋纪念章国庆纪念章宣传达人推广达人

发表于 2021-10-1 22:21:04 | 显示全部楼层
ggnb!!
bilibili:陈公子的话   公众号:陈公子的话
回复

使用道具 举报

20

主题

53

帖子

39

积分

新手上路

Rank: 1

积分
39

中秋纪念章国庆纪念章

发表于 2021-10-1 22:25:56 | 显示全部楼层
ggnb!!
专业混hs圈20年
回复

使用道具 举报

4

主题

99

帖子

36

积分

新手上路

Rank: 1

积分
36

中秋纪念章猫咪币纪念章活跃会员热心会员三好学生

发表于 2021-10-1 22:33:19 | 显示全部楼层
我靠,简直是要爱上你们了,就喜欢这种又硬又能扛饿的代码,一夫当关大杀四方的硬度。大多情况竟都能轻松应对,值得一学
回复

使用道具 举报

159

主题

1105

帖子

618

积分

管理员

Rank: 9Rank: 9Rank: 9

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

论坛大家对油猴研究真的贼他妈细...现在知识密集度搞得我都麻了
混的人。
回复

使用道具 举报

8

主题

124

帖子

162

积分

注册会员

Rank: 2

积分
162

活跃会员热心会员突出贡献三好学生猫咪币纪念章中秋纪念章国庆纪念章

发表于 2021-10-1 22:46:33 | 显示全部楼层
matches这里我补充一下,因为querySelector不匹配自身,即a.querySelector不会匹配a自己,所以matches就是判断a自己是否符合选择器。
回复

使用道具 举报

4

主题

99

帖子

36

积分

新手上路

Rank: 1

积分
36

中秋纪念章猫咪币纪念章活跃会员热心会员三好学生

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

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

使用道具 举报

4

主题

99

帖子

36

积分

新手上路

Rank: 1

积分
36

中秋纪念章猫咪币纪念章活跃会员热心会员三好学生

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

你确定是个大佬,算过了
回复

使用道具 举报

159

主题

1105

帖子

618

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
618
发表于 2021-10-1 23:37:25 | 显示全部楼层
cxxjackie 发表于 2021-10-1 22:46
matches这里我补充一下,因为querySelector不匹配自身,即a.querySelector不会匹配a自己,所以matches就是 ...

这个没考虑到!
混的人。
回复

使用道具 举报

发表回复

本版积分规则

快速回复 返回顶部 返回列表