009 Node 类型

DOM 是也叫文档对象模型,是 HTML 和 XML 文档的一个 API,其描述了一个层次节点树,允许开发人员对文档树进行操作。

Node 接口

DOM 树是由一个一个的节点构成的,DOM1 级中定义了一个 Node 接口,这个接口会由页面中的一个个节点去实现。
在 JavaScript 中,Node 是一个内置对象:

Node
// ƒ Node() { [native code] }

Node 节点共分为 12 种类型,每一种类型都由 Node 对象上的一些常量来表示:

Node.ELEMENT_NODE //1
Node.ATTRIBUTE_NODE //2
Node.TEXT_NODE //3
Node.CDATA_SECTION_NODE //4
Node.ENTITY_REFERENCE_NODE //5
Node.ENTITY_NODE //6
Node.PROCESSING_INSTRUCTION_NODE //7
Node.COMMENT_NODE //8
Node.DOCUMENT_NODE //9
Node.DOCUMENT_TYPE_NODE //10
Node.DOCUMENT_FRAGMENT_NODE //11
Node.NOTATION_NODE //12

JavaScript 中每个节点对象都有一个 nodeType 属性,该属性的值对应着上面的 12 个常量:

document.nodeType //9
document.body.nodeType //1

注:由于 IE 中的所有 DOM 对象都是以 COM 对象呈现的,并且 IE 没有公开 Node 对象的构造函数,因此在 IE 中使用 Node 对象上的节点类型常量可能会发生错误,因此最好使用节点对象的 nodeType 属性,以保证兼容性。
注(2017-08-17):我测试了一下,在 IE9 以下访问 Node 构造函数会抛出错误,在 IE9 以上可以正常使用 Node 的构造函数。

nodeName 和 nodeValue

节点的 nodeName 属性表示元素的标签名,nodeValue 属性表示元素的节点值。对于元素节点 nodeValue 属性将返回 null

document.body.nodeName // "BODY"
document.body.nodeValue // null

childNodes

每个节点都有一个 childNodes 属性,里面保存了一个 NodeList 对象,该对象是一个类数组对象,用来保存一组有序节点。
该对象是一个动态查询的结果,因此节点的变化将会自动在 NodeList 对象中更新。该对象有个 length 属性,表示对象中保存的节点数量,同样,每当节点发生变化时,该属性也会自动变化。

let div = document.createElement("div")
let nodeLists = document.body.childNodes //[]
document.body.appendChild(div)
nodeLists // [div]
nodeList.length // 1
document.body.removeChild(div)
nodeList // [div]
nodeList.length // 1

上面的结果表明:节点 childNodes 属性中保存的 NodeLists 对象是随节点的变化动态更新的。
获取 NodeLists 中的节点可以使用 [] 语法或者 item 方法:

const node1 = nodeLists[1]
const node2 = nodeLists.item(1)

firstChild 和 lastChild

firstChildlastChild 分别指向 NodeLists 中的第一个和最后一个节点,其值分别对应于 NodeLists[0] 以及 NodeLists[NodeLists.length - 1]
注:firstChildlastChild 是父节点的属性,而非 NodeLists 集合的属性。

const body = document.body
body.firstChild === body.childNodes[0] // true
...

perviousSibling 和 nextSibling

每个节点都有一个 previousSiblingnextSibling 属性,分别指向其前一个和后一个兄弟节点。如果元素没有前一个或后一个兄弟节点,那么其 previousSiblingnextSibling 的值为 null

const body = document.body
body.previousSibling // ...
body.nextSibling // null

hasChildNodes 和 ownerDocument

每个节点都一个 hasChildNodes() 方法,用来检验该节点是否有子节点(NodeLists 的长度是否大于 1).

const body = document.body
body.hasChildNodes() // true

同时,每个节点都拥有一个 ownerDocument 属性,该属性执行表示该文档的节点(document),这个属性的意义表示任何节点都属于其所在的文档。

const body = document.body
body.ownerDocument === document // true

操作节点

1.appendChild
该方法用来想父元素的 NodeLists 集合中追加元素,接受一个 Node 对象作为参数。调用该方法返回被追加的节点。

const firstDiv = docuemnt.createElement("div")
const body = document.body
body.appendChild( firstDiv )
body.childNodes // [div]

另外,由于相同的节点不能出现在 DOM 树的多个位置中,因此这里如果重复追加 firstDiv,NodeLists 中的子节点数目不变:

body.appendChild( firstDiv )
body.appendChild( firstDiv )
body.appendChild( firstDiv )
body.childNodes // [div]

同时,如果将已存在的子节点追加到 NodeLists 中,其将会被挪动到 NodeLists 集合的最后一项,其之后的节点将会顶替该节点先前的位置,这个过程同样遵循“同一个节点不能出现 DOM 树的多个位置中”这一原则。

如果传入到 appendChild()中的节点已经是文档的一部分了,那结果就是将该节点从原来的位置
转移到新位置。即使可以将 DOM 树看成是由一系列指针连接起来的,但任何 DOM 节点也不能同时出
现在文档中的多个位置上。因此,如果在调用 appendChild()时传入了父节点的第一个子节点,那么
该节点就会成为父节点的最后一个子节点。

上面引用自 《JavaScript 高级程序设计(第三版)》。
2.insertBefore
appendChild,该方法同样也由父节点调用,该方法接受两个参数:

  • 待插入的节点
  • 参照节点

调用该方法时,将会把带插入的节点插入到参照节点之前,如果该方法的最后一个参数为 null,那个调用这个方法的表现和 appendChild 相同,都是将节点追加到 NodeLists 集合的末尾。调用该方法返回被插入的节点。

const div = document.createElement("div")
const p = document.createElement("p")
const body = document.body
body.appendChild(div)
body.insertBefore(p,div)
body.childNodes // [p, div]

同样,该方法遵循“同一个节点不能出现 DOM 树的多个位置中”原则:

body.insertBefore(p,null)
body.childNodes // [div, p]

3.replaceChild
该方法用来对节点进行替换,接受两个参数:

  • 新节点
  • 被替换的节点

调用这个方法时,被替换的节点将被移除,并由新的节点代替:

const div = document.createElement("div")
const p = document.createElement("p")
const a = document.createElement("a")
const body = document.body
body.appendChild(div)
body.appendChild(p)
body.childNodes // [div, p]
// 替换节点
body.replaceChild(a,body.firstChild)
body.childNodes // [a, p]

4.removeChild
如果只想从文档树中移除某个节点,需要调用 removeChild 方法,该方法接受需要被移除的节点作为参数,返回被移除的节点:

const div = document.createElement("div")
const p = document.createElement("p")
const body = document.body
body.appendChild(div)
body.appendChild(p)
body.childNodes // [div, p]
// 移除节点
body.removeChild(p) // p
body.childNodes // [div]

5.cloneNode
该方法用来对节点进行复制,接受一个标志参数 true 或者 false,表示是否执行深复制,当执行深复制时,会复制节点本身以及其所有的子节点。当进行浅复制时,仅复制节点本身。

const div = document.createElement("div")
const p = document.createElement("p")
const body = document.body
body.appendChild(div)
body.appendChild(p)
// 进行浅复制
body_shallowcpy = body.cloneNode()
// 进行深复制
body_deepcpy = body.cloneNode(true)
body_shallowcpy .childNodes // []
body_deepcpy .childNodes // [div, p]

总结

本文主要介绍了 Node 类型以及操作 DOM 的几个方法。总结一下:

  • Node 是一个接口,规定了 DOM 节点的一些实现
  • 所有的节点都会实现 Node 接口,每个节点都有一个 nodeType 属性,和 Node 对象中的一些常量一一对应,表示该节点的类型
  • 由于 IE 的 DOM 不是使用原生 JavaScript 实现的,因此无法获取到 Node 对象上的这些常量,因此最好使用数字值进行节点类型的判断,以提高兼容性
  • nodeNamenodeValue 属性
  • childNodes 是一个包含了元素所有子节点的一个集合,该集合是动态的,DOM 树中的任何变化都会反映到这个集合中
  • firstChildlastChild
  • previousSiblingnextSibling
  • hasChildNodes() 可以用来检验元素是否有子节点
  • 每个节点元素都拥有一个 ownerDocument 属性,指向文档节点,该属性的意义表示任何节点都属于其所在的文档
  • 操作节点的几个方法
  • cloneNode() 方法用来复制节点,有深复制和浅复制之分

使用 JavaScript 操作 DOM 可以让网页出现多种多样的变化,JavaScript 中与 DOM 相关的方法很多很多,也是使大多数前端er 们感到陌生或者似懂非懂的地方(正经脸:)),因为我们平时写代码时大多用的是一些 DOM 操作的封装,比如 jQuery 库,甚至如果你使用 Vue 或者 React 之类的框架,那操作 DOM 的机会更少了。不过学习这块的知识可以扎实我们的基础,虽然不一定有太多的使用机会,但你可以推测这些框架或者库是怎么实现这些 DOM 操作的,对技术和思维上的提升会有不少的帮助。
还是那句话:如果哪天遗忘了,请随时回过头来看看~

完。

你可能感兴趣的:(009 Node 类型)