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

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

[复制链接]
  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    620

    主题

    5085

    回帖

    5959

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    5959

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

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

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
  • TA的每日心情

    前天 22:44
  • 签到天数: 416 天

    [LV.9]以坛为家II

    148

    主题

    423

    回帖

    1133

    积分

    版主

    积分
    1133

    油中2周年生态建设者

    发表于 2021-10-1 22:21:04 | 显示全部楼层
    ggnb!!
    I don't hate programming but the fucking world.
    回复

    使用道具 举报

  • TA的每日心情

    2024-1-3 11:32
  • 签到天数: 18 天

    [LV.4]偶尔看看III

    115

    主题

    104

    回帖

    429

    积分

    高级工程师

    积分
    429

    油中2周年油中3周年

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

    使用道具 举报

  • TA的每日心情
    开心
    2024-3-3 00:00
  • 签到天数: 117 天

    [LV.6]常住居民II

    27

    主题

    588

    回帖

    521

    积分

    专家

    积分
    521

    油中2周年生态建设者油中3周年挑战者 lv2

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

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    620

    主题

    5085

    回帖

    5959

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    5959

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

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

    论坛大家对油猴研究真的贼他妈细...现在知识密集度搞得我都麻了
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    857

    回帖

    1356

    积分

    荣誉开发者

    积分
    1356

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

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

    使用道具 举报

  • TA的每日心情
    开心
    2024-3-3 00:00
  • 签到天数: 117 天

    [LV.6]常住居民II

    27

    主题

    588

    回帖

    521

    积分

    专家

    积分
    521

    油中2周年生态建设者油中3周年挑战者 lv2

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

    呵呵,这才是追求知识的范儿,掐指一算,这个论坛是要火的一塌糊涂的最后
    入驻爱发电 让这世界充满爱 https://afdian.net/a/vpannice
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-3-3 00:00
  • 签到天数: 117 天

    [LV.6]常住居民II

    27

    主题

    588

    回帖

    521

    积分

    专家

    积分
    521

    油中2周年生态建设者油中3周年挑战者 lv2

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

    你确定是个大佬,算过了
    入驻爱发电 让这世界充满爱 https://afdian.net/a/vpannice
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    620

    主题

    5085

    回帖

    5959

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    5959

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

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

    这个没考虑到!
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

    发表回复

    本版积分规则

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