【问题标题】:How can I test the equality of two NodeLists?如何测试两个 NodeList 的相等性?
【发布时间】:2019-01-28 05:41:19
【问题描述】:

假设我有一个自定义函数,我希望它返回一个 NodeList:

getNodeList('foo');

我希望这个 NodeList 与从以下位置返回的 NodeList 相同:

document.querySelectorAll('.foo');

如何检查我的预期是否正确?

这样做不起作用:

getNodeList('foo') == document.querySelectorAll('.foo')

我确信这不起作用有一个很好的技术原因,因为 document.querySelectorAll('.foo') == document.querySelectorAll('.foo') 也不起作用,我认为这是意料之中的。

如何测试两个 NodeList 是否包含相同的 HTML 节点?

【问题讨论】:

  • 很多很好的答案,我不知道该投给哪个:(

标签: javascript object nodelist


【解决方案1】:

一个更简单但可能更快的@mauroc8 版本:

     const nodeEq = (A, B) => {
        A = Array.from(A)
        B = Array.from(B)
        if (A.length !== B.length) return false

        return A.every((a, b) => a.innerHTML === B[b].innerHTML)
      }

【讨论】:

    【解决方案2】:

    数组相等是通过引用而不是内容来实现的。

    let a = [1, 2, 3], b = [1, 2, 3]
    let c = a
    a == c // => true, since both refer to `a`
    a == b // => false
    

    如果要比较两个类似数组的对象,则必须按索引进行比较。

    function eq(A, B) {
      if (A.length !== B.length) return false;
      for (let i = 0; i < A.length; i++) {
        if (A[i] !== B[i]) return false;
      }
      return true;
    }
    

    当然,你总是可以使用一些函数式编程魔法:

    let arrayEq = (A, B) => A.length === B.length && A.every((e, i) => e === B[i]);
    

    但它只有在 A 是一个数组(不是 NodeList)时才有效。


    那就试试

    eq(getNodeList('foo'), document.querySelectorAll('.foo'))
    

    arrayEq(Array.from(getNodeList('foo')), Array.from(document.querySelectorAll('.foo'))
    

    【讨论】:

    • 只需使用A 和回调调用Array.prototype.every,它也可以在NodeLists 上工作。无需创建中间数组。
    • 或者,如果你不想做原型窃取魔法,arrayEq(Array.from(a), Array.from(b))
    • 谢谢,我在答案中添加了后一个建议
    • 谢谢,这似乎是一个很好的答案。一旦我尝试过各种解决方案,我会将其中一个标记为已接受。
    【解决方案3】:

    在需要比较可能不共享相同节点顺序但其他节点相等的节点列表后,我重新审视了这一点,并提出了这个:

    export default function nodeListsAreEqual(actual, expected) {
        if ((actual.length || Object.keys(actual).length) !== (expected.length || Object.keys(expected).length)) {
            return false;
        }
    
        return Array.from(actual).every(actualNode => {
            return Array.from(expected).some(expectedNode => actualNode === expectedNode)
        });
    }
    

    【讨论】:

      【解决方案4】:

      如果您不介意安装第三方库,请从 NPM 获取 deep-equal 并执行以下操作:

      deepEqual(Array.from(getNodeList('foo')), Array.from(document.querySelectorAll('.foo')))
      

      这确保您的列表只计算一次,并将列表比较的所有血腥细节封装到一个单独的函数中。您的代码应该简单地调用一个相等函数,而不是将您的应用程序关注点与列表结构的低级遍历混为一谈。 (但你可能已经知道了!)

      如果您不喜欢 Array.from 的冗长,请使用 splats:

      deepEqual([...getNodeList('foo')], [...document.querySelectorAll('.foo')])
      

      如果效率很重要,您需要进行一些分析。

      【讨论】:

      • 我实际上已经在使用“deep-extend”,所以我也可以包含deep-equal...
      • 值得一试;绝对是配置文件和测试。 NodeLists 有点像你所知道的(我相信它们就像 HTMLCollections 一样“活”),并且在枚举时它们工作得很好,但对于像平等 Array.from 这样的事情通常是必需的。但是在实现一个实际的数组时,整个性能都会受到影响,所以 YMMV。 :) (至少使用deepEqual 调用代码看起来更好。:))
      • 我刚刚用nodejs.org/api/… 试过这个,它可能以相同的方式工作,但不幸的是它不起作用 - 它不比较实际的 DOM 节点,所以任何 2 个具有相同的 NodeList节点数将始终返回 true。
      • 啊,有道理。 shallowEqual 比较在这里可能要好得多(因为这就是公认的答案本质上所做的)。如果事情不适合您,可能是因为节点是可变的,并且当您调用 querySelectorAll 与创建 NodeList 时节点内部的某些内容不同,但对象身份相同。 .. 只是一个想法。很高兴您找到了可接受的答案。
      【解决方案5】:

      到目前为止,您所拥有的似乎还可以,但效率低下(您可能会重新计算 document.querySelectorAll(...)indexOf 很多次)。

      还有一个bug:如果querySelectorAll返回的元素多于第一个节点列表,但它们在其他方面相等,你的函数将返回true

      您还可以进一步简化比较:

      function nodeListsAreEqual( list1, list2 ) {
          if ( list1.length !== list2.length ) {
              return false;
          }
          return Array.from( list1 ).every( ( node, index ) => node === list2[ index ] );
      }
      

      【讨论】:

      • 谢谢 - 你对这个错误的看法是对的,感谢你提醒我效率低下 - 似乎是一个很好的答案。
      • 我最终采用了这种方法,但@mauroc8 的回答似乎同样好
      【解决方案6】:

      我想出了这个似乎可行的方法,使用 ES6 特性:

      const isEqual = [...getNodeList('foo')].every((node, index) => {
          return Array.from(document.querySelectorAll('.foo')).indexOf(node) === index
      });
      

      本质上,我们测试第一个 NodeList 中的每个项目是否存在于第二个 NodeList 中的相同索引处。如果 NodeLists 之间有任何差异,这应该返回 false。

      【讨论】:

      • 如果第二个NodeList更长了怎么办?
      • @RayToal 在这种情况下,我的情况似乎无法按预期工作,正如 jnylen 的回答所指出的那样