Document.querySelector 返回 null 直到使用 DevTools 检查元素

Posted

技术标签:

【中文标题】Document.querySelector 返回 null 直到使用 DevTools 检查元素【英文标题】:Document.querySelector returns null until element is inspected using DevTools 【发布时间】:2020-12-16 15:57:42 【问题描述】:

我正在尝试创建一个 Chrome 扩展程序,用于在 Facebook 上查找“赞助”帖子并将其删除。

在执行此操作时,我注意到 Google Chrome 在 Facebook.com 上的这种相当奇怪的行为,其中对现有元素的某些类型的查询(在我的情况下为 document.querySelector('a[href*="/ads/about"]');)将返回 null。但是,如果您“检查”-单击它们(使用检查工具或 CTRL+SHIFT+C),它们将显示在 DevTools 中,然后在控制台中再次运行查询将显示该元素。无需对页面进行任何滚动、移动、调整大小或任何操作。

这可以使用上面的说明轻松复制,但为了清楚起见,我制作了以下视频,准确显示了奇怪的行为:

https://streamable.com/mxsf86

这是某种 dom 查询缓存问题吗?你有没有遇到过类似的事情?谢谢

编辑:问题现在已简化为返回 null 的查询,直到元素悬停,这不再是与 DevTools 相关的问题。

【问题讨论】:

"然后在控制台中再次运行查询将显示该元素。" 听起来就像您第一次运行代码时该元素根本不存在,您需要等待它被添加到 DOM 中。 观看视频后:您是否确定该链接不会在点击或鼠标悬停较长时间或其他任何情况下显示? 也许,您尝试获取的 div 是使用 React Portals 构建并放置在另一个 DOM 树中的。 @VLAZ 我在页面上看到它的实际时间大约 3-4 秒后运行查询......那么它怎么可能“不被添加到 DOM”呢? Sponsored 是一个带有 tabindex 0 的role="button",它在点击和悬停时重新加载内容。您也可以在network 选项卡中看到它。 a 在此之前根本不存在。是否将其悬停在 DevTools 打开与否都没关系。 【参考方案1】:

正如已经注意到的,赞助商链接在某些鼠标事件发生之前根本不在其位置。一旦鼠标事件发生,元素就会被添加到 DOM 中,据说这就是 Facebook 避免人们太容易抓取它的方式。

因此,如果您想查找赞助商链接,则需要执行以下操作

找出导致添加链接的确切事件 进行实验,直到发现如何以编程方式生成该事件 实现一种爬行算法,该算法在墙上长时间滚动,然后引发给定事件。届时您可能会获得许多赞助商链接

注意:赞助商链接由公司支付,如果他们的广告位被不感兴趣的机器人占用,他们不会很高兴。

【讨论】:

【参考方案2】:

我解决这个问题的方法如下:

// using an IIFE ("Immediately-Invoked Function Expression"):
(function() 
    'use strict';

// using Arrow function syntax to define the callback function
// supplied to the (later-created) mutation observer, with
// two arguments (supplied automatically by that mutation
// observer), the first 'mutationList' is an Array of
// MutationRecord Objects that list the changes that were
// observed, and the second is the observer that observed
// the change:
const nodeRemoval = (mutationList, observer) => 

  // here we use Array.prototype.forEach() to iterate over the
  // Array of MutationRecord Objects, using an Arrow function
  // in which we refer to the current MutationRecord of the
  // Array over which we're iterating as 'mutation':
  mutationList.forEach( (mutation) => 

    // if the mutation.addedNodes property exists and
    // also has a non-falsy length (zero is falsey, numbers
    // above zero are truthy and negative numbers - while truthy -
    // seem invalid in the length property):
    if (mutation.addedNodes && mutation.addedNodes.length) 

        // here we retrieve a list of nodes that have the
        // "aria-label" attribute-value equal to 'Advertiser link':
        mutation.target.querySelectorAll('[aria-label="Advertiser link"]')
          // we use NodeList.prototype.forEach() to iterate over
          // the returned list of nodes (if any) and use (another)
          // Arrow function:
          .forEach(
            // here we pass a reference to the current Node of the
            // NodeList we're iterating over, and use
            // ChildNode.remove() to remove each of the nodes:
            (adLink) => adLink.remove() );
    
  );
,
      // here we retrieve the <body> element (since I can't find
      // any element with a predictable class or ID that will
      // consistently exist as an ancestor of the ad links):
      targetNode = document.querySelector('body'),

      // we define the types of changes we're looking for:
      options = 
          // we're looking for changes amongst the
          // element's descendants:
          childList: true,
          // we're not looking for attribute-changes:
          attributes: false,
          (if this is false, or absent, we look only to
          changes/mutations on the target element itself):
          subtree: true
,
      // here we create a new MutationObserver, and supply
      // the name of the callback function:
      observer = new MutationObserver(nodeRemoval);

    // here we specify what the created MutationObserver
    // should observe, supplying the targetNode (<body>)
    // and the defined options:
    observer.observe(targetNode, options);

)();

我意识到在您的问题中,您正在寻找与不同属性和属性值 (document.querySelector('a[href*="/ads/about"]')) 匹配的元素,但由于该属性值与我自己的情况不匹配,因此我无法在我的代码,但它应该像替换一样简单:

mutation.target.querySelectorAll('[aria-label="Advertiser link"]')

与:

mutation.target.querySelector('a[href*="/ads/about"]')

虽然值得注意的是querySelector() 将只返回匹配选择器的第一个节点,或者null;所以你可能需要在你的代码中加入一些检查。

虽然上面的代码看起来相当多,但未注释的只是:

(function() 
    'use strict';

const nodeRemoval = (mutationList, observer) => 
  mutationList.forEach( (mutation) => 
    if (mutation.addedNodes && mutation.addedNodes.length) 
        mutation.target.querySelectorAll('[aria-label="Advertiser link"]').forEach( (adLink) => adLink.remove() );
    
  );
,
      targetNode = document.querySelector('body'),
      options = 
          childList: true,
          attributes: false,
          subtree: true
,
      observer = new MutationObserver(nodeRemoval);

    observer.observe(targetNode, options);

)();

参考资料:

Array.prototype.forEach()。 Arrow Functions。 childNode.remove()MutationObserver() Interface。 NodeList.prototype.forEach()

【讨论】:

以上是关于Document.querySelector 返回 null 直到使用 DevTools 检查元素的主要内容,如果未能解决你的问题,请参考以下文章

document.querySelector()方法document.querySelectorA()

Document.querySelector 返回 null 直到使用 DevTools 检查元素

document.querySelector()方法

Document.querySelector() 未显示所有元素

选择符API

乘法逆元模板