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
firstChild
和 lastChild
分别指向 NodeLists 中的第一个和最后一个节点,其值分别对应于 NodeLists[0]
以及 NodeLists[NodeLists.length - 1]
。
注:firstChild
和 lastChild
是父节点的属性,而非 NodeLists 集合的属性。
const body = document.body
body.firstChild === body.childNodes[0] // true
...
perviousSibling 和 nextSibling
每个节点都有一个 previousSibling
和 nextSibling
属性,分别指向其前一个和后一个兄弟节点。如果元素没有前一个或后一个兄弟节点,那么其 previousSibling
和 nextSibling
的值为 null
。
const body = document.body
body.previousSibling // <head>...</head>
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 对象上的这些常量,因此最好使用数字值进行节点类型的判断,以提高兼容性
-
nodeName
和nodeValue
属性 -
childNodes
是一个包含了元素所有子节点的一个集合,该集合是动态的,DOM 树中的任何变化都会反映到这个集合中 -
firstChild
和lastChild
-
previousSibling
和nextSibling
-
hasChildNodes()
可以用来检验元素是否有子节点 - 每个节点元素都拥有一个
ownerDocument
属性,指向文档节点,该属性的意义表示任何节点都属于其所在的文档 - 操作节点的几个方法
-
cloneNode()
方法用来复制节点,有深复制和浅复制之分
使用 JavaScript 操作 DOM 可以让网页出现多种多样的变化,JavaScript 中与 DOM 相关的方法很多很多,也是使大多数前端er 们感到陌生或者似懂非懂的地方(正经脸:)),因为我们平时写代码时大多用的是一些 DOM 操作的封装,比如 jQuery 库,甚至如果你使用 Vue 或者 React 之类的框架,那操作 DOM 的机会更少了。不过学习这块的知识可以扎实我们的基础,虽然不一定有太多的使用机会,但你可以推测这些框架或者库是怎么实现这些 DOM 操作的,对技术和思维上的提升会有不少的帮助。
还是那句话:如果哪天遗忘了,请随时回过头来看看~
完。
网友评论