let arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0);
当然,使用 ES6 的 Array.from()静态方法,可以替换这种笨拙的方式: let arrayOfNodes = Array.from(someNode.childNodes); 每个节点都有一个 parentNode 属性,指向其 DOM 树中的父元素。childNodes 中的所有节点都 有同一个父元素,因此它们的 parentNode 属性都指向同一个节点。此外,childNodes 列表中的每个 节点都是同一列表中其他节点的同胞节点。而使用 previousSibling 和 nextSibling 可以在这个列 表的节点间导航。这个列表中第一个节点的 previousSibling 属性是 null,最后一个节点的 nextSibling 属性也是 null,如下所示:
if (someNode.nextSibling === null){
alert("Last node in the parent's childNodes list.");
} else if (someNode.previousSibling === null){
alert("First node in the parent's childNodes list.");
}
注意,如果childNodes中只有一个节点,则它的previousSibling和nextSibling属性都是 null。
父节点和它的第一个及最后一个子节点也有专门属性:firstChild 和 lastChild 分别指向 childNodes 中的第一个和最后一个子节点。
someNode.firstChild 的值始终等于 someNode. childNodes[0],而 someNode.lastChild 的值始终等于 someNode.childNodes[someNode. childNodes.length-1]。如果只有一个子节点,则 firstChild 和 lastChild 指向同一个节点。如 果没有子节点,则 firstChild 和 lastChild 都是 null。上述这些节点之间的关系为在文档树的节 点之间导航提供了方便。图 14-2 形象地展示了这些关系。
有了这些关系,childNodes 属性的作用远远不止是必备属性那么简单了。这是因为利用这些关系 指针,几乎可以访问到文档树中的任何节点,而这种便利性是 childNodes 的最大亮点。还有一个便利 的方法是 hasChildNodes(),这个方法如果返回 true 则说明节点有一个或多个子节点。相比查询 childNodes 的 length 属性,这个方法无疑更方便。
最后还有一个所有节点都共享的关系。ownerDocument 属性是一个指向代表整个文档的文档节点 的指针。所有节点都被创建它们(或自己所在)的文档所拥有,因为一个节点不可能同时存在于两个或者多个文档中。这个属性为迅速访问文档节点提供了便利,因为无需在文档结构中逐层上溯了。
因为所有关系指针都是只读的,所以 DOM 又提供了一些操纵节点的方法。最常用的方法是 appendChild(),用于在 childNodes 列表末尾添加节点。添加新节点会更新相关的关系指针,包括 父节点和之前的最后一个子节点。appendChild()方法返回新添加的节点,如下所示:
let returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); // true
alert(someNode.lastChild == newNode); // true
如果把文档中已经存在的节点传给 appendChild(),则这个节点会从之前的位置被转移到新位置。 5 即使 DOM 树通过各种关系指针维系,一个节点也不会在文档中同时出现在两个或更多个地方。因此, 如果调用 appendChild()传入父元素的第一个子节点,则这个节点会成为父元素的最后一个子节点, 如下所示:
// 假设 someNode 有多个子节点
let returnedNode = someNode.appendChild(someNode.firstChild); alert(returnedNode == someNode.firstChild); // false alert(returnedNode == someNode.lastChild); // true
// 作为最后一个子节点插入
returnedNode = someNode.insertBefore(newNode, null); 9 alert(newNode == someNode.lastChild); // true
如果想把节点放到 childNodes 中的特定位置而不是末尾,则可以使用 insertBefore()方法。 这个方法接收两个参数:要插入的节点和参照节点。调用这个方法后,要插入的节点会变成参照节点的 前一个同胞节点,并被返回。如果参照节点是 null,则 insertBefore()与 appendChild()效果相 同,如下面的例子所示:
// 作为新的第一个子节点插入
returnedNode = someNode.insertBefore(newNode, someNode.firstChild); alert(returnedNode == newNode); // true
alert(newNode == someNode.firstChild); // true
// 插入最后一个子节点前面
returnedNode = someNode.insertBefore(newNode, someNode.lastChild); alert(newNode == someNode.childNodes[someNode.childNodes.length - 2]); // true
appendChild()和 insertBefore()在插入节点时不会删除任何已有节点。相对地, replaceChild()方法接收两个参数:要插入的节点和要替换的节点。要替换的节点会被返回并从文档 树中完全移除,要插入的节点会取而代之。下面看一个例子:
let returnedNode = someNode.replaceChild(newNode, someNode.firstChild); 13
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
使用 replaceChild()插入一个节点后,所有关系指针都会从被替换的节点复制过来。虽然被替换