如何测试两个NodeLists的相等性?

4

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

getNodeList('foo');

我期望这个NodeList与以下代码返回的NodeList相同:

document.querySelectorAll('.foo');

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

尝试以下代码并不起作用:

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

我相信这个不起作用的原因是有良好的技术原因,因为document.querySelectorAll('.foo')== document.querySelectorAll('.foo')也不起作用,我认为这是可以预料的。

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


有很多好的答案,我不知道该投哪一个票:( - ESR
6个回答

6

数组的相等性是通过引用而不是内容来确定的。

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'))

只需使用Array.prototype.every和回调函数调用A,它也可以在NodeList上工作。无需创建中间数组。 - CertainPerformance
或者,如果你不想使用原型窃取魔法,可以使用arrayEq(Array.from(a), Array.from(b)) - Amadan
谢谢,我已经将后面的建议添加到答案中。 - mauroc8
谢谢,看起来是个不错的答案。我会试着使用各种解决方案,然后标记一个为已接受的答案。 - ESR

2

你目前的代码看起来还可以,但效率不高(你可能会多次重新计算document.querySelectorAll(...)indexOf)。

还有一个错误:如果querySelectorAll返回的元素比第一个节点列表多,但它们是相等的,你的函数将返回true

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

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

1
谢谢 - 你说得对,关于这个漏洞的提醒和效率问题的指出,非常感谢 - 看起来是一个很好的答案。 - ESR
最终我采用了这种方法,但是@mauroc8的答案似乎也很好。 - ESR

2

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

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

这样可以确保您的列表仅计算一次,并将所有列表比较的细节封装到单独的函数中。您的代码应该只调用一个相等性函数,而不是将应用程序问题与低级别的列表结构遍历混合在一起。 (但您可能已经知道了!)
如果您不喜欢Array.from的冗长,请使用展开运算符:
deepEqual([...getNodeList('foo')], [...document.querySelectorAll('.foo')])

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

我实际上已经在使用“deep-extend”,所以也许加入deep-equal会更有意义... - ESR
值得一试;一定要进行配置文件和测试。正如您所知,NodeList有点奇怪(我相信它们是“活动的”,就像HTMLCollections一样),当枚举时它们可以很好地工作,但对于诸如等式之类的事情,通常需要使用Array.from。但是,实现实际数组会带来整体性能损失,因此结果可能因人而异。:)(至少使用deepEqual调用时代码看起来更漂亮。:)) - Ray Toal
我刚刚尝试了一下 https://nodejs.org/api/assert.html#assert_assert_deepequal_actual_expected_message,它可能是以相同的方式工作的,但不幸的是它并不起作用 - 它不会比较实际的DOM节点,因此任何具有相同节点数量的2个NodeLists将始终返回true。 - ESR
1
啊,明白了。在这里使用shallowEqual比较可能更好(因为这基本上就是被接受的答案所做的)。如果你遇到问题,可能是因为节点是可变的,并且在调用querySelectorAll和创建NodeList时节点内部的某些内容不同,但对象标识相同...只是一个想法。很高兴你找到了一个被接受的答案。 - Ray Toal

1

我想出了一种看起来可行的方法,使用ES6特性:

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

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


如果第二个NodeList更长呢? - Ray Toal
@RayToal 在这种情况下,正如jnylen的答案所指出的那样,我的条件似乎不会按预期工作。 - ESR

0

一个更简单但可能更快的@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)
      }

0

我重新审视了这个问题,因为需要比较可能没有相同节点顺序的NodeLists,但是节点本身是相等的,并想出了以下解决方案:

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)
    });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接