isSameNode

JavaScript

Node.isSameNode 这个 API 的作用,是判断另一个 Node 节点和当前节点是否是相同的。举例来说:

const a = document.querySelector('#a');
const b = document.querySelector('#b');
const c = a;

a.isSameNode(c); // => true
a.isSameNode(b); // => false

因为在一个同一个 document 中,一个 Node 实际只有一个引用,因此 .isSameNode API 的实际效果其实和 ===== 运算是一致的。简单来说,上面的代码,可以等价于:

a === c; // => true
a === b; // => false

DOM (Living Standard) 规范中,也可以看到相关的注释,说明 .isSameNode 本质上只是因为历史原因而给出的 === 的别名(alias)。

然而在某些有限的场景下,.isSameNode 依然有发挥的应用场景,目前可以想到的有以下几点:

  1. 在节点相关算法(如 Diff 算法)中作为抽象方法直接使用。比如,在 morphdom 中,就使用了 .isSameNode 这个 API 来比较两个节点是否相同,从而节省比较的次数(源码)。根据 morphdom 给出的文档可以看到,morphdom 的算法也支持对 virtual dom 进行比较,只需要 virtual dom 也对节点实现了相应的 .isSameNode API,就有可能可以在比较的时候节省一定的计算次数。这里,.isSameNode 在 morphdom 中就被作为抽象方法使用了,算法本身并不在意真正在 diff 的对象是真实的 DOM 还是 virtual DOM,只要节点实现了符合要求的 API,算法就可以正确的进行。
  2. 通过重写方法来达到“代理节点”的功能。现在大多数的 UI 库,都通过声明式的方式来定义组件。在这种情况下,开发者并不需要显示的写出在何时通过何种方式创建或更新一个节点,只需要写出 state => UI 这样的映射函数,UI 库就会在 state 更新后,通过映射函数去得到新的 UI 组件,然后通过 diff 算法去计算得到需要修改的部分,最终将必要的部分进行更新。在这种情况下,就没有办法通过 === 去比较两个节点是否相同了,因为流程上是需要通过新的 state 生成节点,然后再和已有的 Node 进行比较。这种情况下,通过改写 .isSameNode 就可以达到人为控制的目的。

举一个 nanocomponent 中提到的例子:

const html = require('nanohtml');

const el1 = html`<div>pink is the best</div>`;
const el2 = html`<div>blue is the best</div>`;

// 对 el1 进行代理操作
const proxy = html`<div></div>`;
proxy.isSameNode = function (targetNode) {
  return (targetNode === el1);
}

el1.isSameNode(el1);   // true
el1.isSameNode(el2);   // false
proxy.isSameNode(el1); // true
proxy.isSameNode(el2); // false

虽然 proxyel1 并不是真的一样的两个节点,但是因为对 isSameNode 进行了改写,因而在 diff 算法中,两个节点会被当作是一致的。这有助于节省比较的次数。

.isSameNode API 的支持情况,可以查看 Can I Use;文档可以参考 MDN