目录
说明
- DOM:文档对象模型 Document Object Model
- 通过 DOM 我们可以添加、删除、修改页面的各个部分,实现交互效果。
- 任何 HTML 页面都可以用 DOM 表达为一个由节点构成的层级结构。
- 在 HTML 页面中 Document 节点就是所有节点的根节点。
- 而 <html> 元素则是 Document 节点的唯一子节点,我们将 <html> 元素称为文档元素。
- 文档元素是文档最外层的元素,其它元素都在该元素之内,在HTML页面中文档元素就是<html> 元素。
节点
- html 文件当中的所有内容都可以称之为是节点包括:注释、文本、标签、换行符、属性等。
- 在 JavaScript 中所有节点类型都继承自 Node 类型,所以所有节点类型都共享相同的基本属性和方法。
- 节点的类型由 JavaScript 定义在 Node 类型上的12个数值表示。
节点的类型
常量 | nodeType 值 | 描述 | 状态 |
---|---|---|---|
Node.ELEMENT_NODE | 1 | 标签节点,例如:<p> <span> | |
Node.ATTRIBUTE_NODE | 2 | 元素属性 | 已弃用 |
Node.TEXT_NODE | 3 | 元素或属性中的文本内容 | |
Node.CDATA_SECTION_NODE | 4 | XML 中的 文档中的 CDATA 部分 | |
Node.ENTITY_REFERENCE_NODE | 5 | XML 中的 实体引用 | 已弃用 |
Node.ENTITY_NODE | 6 | XML 中的 实体 | 已弃用 |
Node.PROCESSING_INSTRUCTION_NODE | 7 | XML 中的 处理指令 | |
Node.COMMENT_NODE | 8 | 注释 | |
Node.DOCUMENT_NODE | 9 | 表示整个文档 Document 也就是 DOM 树的根节点 | |
Node.DOCUMENT_TYPE_NODE | 10 | XML中的 表示文档型网页的节点 | |
Node.DOCUMENT_FRAGMENT_NODE | 11 | 表示一个节点,但它不是 DOM 树的一部分,只相当于一个储存其它节点的变量,所以它的变化不会触发 DOM 树的重新渲染,可以通过调用 appendChild 方法来将该节点添加到 DOM 树中。 | |
Node.NOTATION_NODE | 12 | XML 中的 代表 DTD 中声明的符号 | 已弃用 |
节点的层级
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<h1>have a good time oneself!</h1>
</body>
</html>
- 在DOM中HTML文档可以表示为一个由节点构成的层级结构,比如上面这段代码:
- <head> 元素是 <html> 元素的子元素
- <html> 元素是 <head> 元素的父元素
- <head> 元素和 <body> 元素则是兄弟元素他们有共同的父元素 <html>
nodeType
- 每个节点都有 nodeType 属性,表示该节点的类型。节点类型由定义在 Node 类型上的 12 个数值常量表示。
-
<div id="box"> <ul id="list"> <li>aaaaa</li> <li>aaaaa</li> <li>aaaaa</li> <li>aaaaa</li> <li>aaaaa</li> </ul> </div> <script> const list = document.getElementById('list') console.log(list.nodeType) // 1 </script>
nodeName
- 每个节点都有 nodeName 属性,该属性表示节点的名称。
- 元素节点的 nodeName,将返回标签名称。
- 属性节点的 nodeName,将返回属性名称。
- 文本节点的 nodeName,将返回 #text。
- 文档节点的 nodeName,将返回 #document。
- 对于其它节点类型,则会返回不同的名称。
-
<div id="box"> <ul id="list"> <li>aaaaa</li> <li>aaaaa</li> <li>aaaaa</li> <li>aaaaa</li> <li>aaaaa</li> </ul> </div> <script> const list = document.getElementById('list') console.log(list.nodeName) // UL </script>
nodeValue
- 文本节点的 nodeValue 属性包含文本,对于属性节点的 nodeValue 属性包含属性值。
- 文档节点和标签节点的 nodeValue 属性是不可用的。
-
<div id="box"> <ul id="list"> <li id="content">aaaaa</li> </ul> </div> <script> const content = document.getElementById('content') console.log(content.nodeValue) // undefined console.log(content.childNodes[0].nodeValue) // aaaaa // childNodes 可以获取该标签的子元素数组从而来获得其中的 aaaaa 文本 </script>
节点的关系
childNodes
- 每个节点都有一个 childNodes 属性,其中包含一个 NodeList 类数组对象。
- NodeList 用于有序的,存储该节点的子节点。其拥有 length 属性,length 属性的值可以用来表示当前 NodeList 中的节点数量。
- 当 DOM 结构发生变化时 NodeList 也发生相对的变化。
-
<div id="box"> <ul id="list"> <li id="content">aaaaa</li> <li>aaaaa</li> </ul> </div> <script> const list = document.getElementById('list') const content = document.getElementById('content') console.log(list.childNodes) // NodeList(5) // 0: text // 1: li#content // 2: text // 3: li // 4: text // length: 5 // [[Prototype]]: NodeList console.log(content.childNodes) // NodeList(1) // 0: text // length: 1 // [[Prototype]]: NodeList console.log(content.childNodes[0].nodeValue) // aaaaa </script>
parentNode
- 每个节点都有一个 parentNode 属性,指向他 DOM 树中的父元素。
- childNodes 中的所有节点都拥有同一个父元素,所以他们的 parentNode 属性指向同一个元素
-
<div id="box"> <ul id="list"> <li id="content">aaaaa</li> <li id="details">aaaaa</li> </ul> </div> <script> const content = document.getElementById('content') const details = document.getElementById('details') console.log(content.parentNode) // <ul id="list"></ul> console.log(details.parentNode) // <ul id="list"></ul> </script>
previousSibling 和 nextSibling
- childNodes 列表中的每个节点,都是同一列表中,其它节点的同胞节点。
- previousSibling 属性是当前节点在同一列表中的上一个节点。
- nextSibling 属性是当前节点在同一列表中的下一个节点。
- 所以 childNodes 列表中,第一个节点的 previousSibing 属性是 null,最后一个节点的 nextSibling 属性是 null。
- 如果 childNodes 列表中只有一个节点,那么该节点的 previousSibling 属性和 nextSibling 属性都是 null。
-
<div id="box"><ul id="list"><li id="content">aaaaa</li><li id="details">aaaaa</li><li id="matter">aaaaa</li></ul></div> <!-- 之所以将代码写成一行是因为换行符也是节点 --> <script> const content = document.getElementById('content') console.log(content.previousSibling) // null console.log(content.nextSibling) // <li id="details">aaaaa</li> const details = document.getElementById('details') console.log(details.previousSibling) // <li id="content">aaaaa</li> console.log(details.nextSibling) // <li id="matter">aaaaa</li> const matter = document.getElementById('matter') console.log(matter.previousSibling) // <li id="details">aaaaa</li> console.log(matter.nextSibling) // null const list = document.getElementById('list') console.log(list.previousSibling) // null console.log(list.nextSibling) // null </script>
firstChild 和 lastChild
- 如果想要获取父节点的第一个子节点或,最后一个子节点可以使用专门的属性:
- firstChild 指向 childNodes 中的第一个子节点。
- lastChild 指向 childNodes 中的最后一个子节点。
- 如果该父节点只有一个子节点,那么 firstChild 和 lastChild 指向同一个节点。
- 如果该父节点没有子节点,那么 firstChild 和 lastChild 都是 null。
上述这些节点之间的关系为我们操作文档树时,节点之间的导航提供了方便,这些关系用下图表现的更加明了。
hasChildNodes ()
- 由上文可知我们可以通过判断节点 childNodes 属性的 length ,来判断该节点是否有子节点。
- 但是还有一个更加便利的方法:hasChildNodes 方法,如果该方法返回 true 则说明该节点有一个或多个子节点,如果返回 false 则说明该节点没有子节点。
-
<body> <div id="a"> <div id="b">Hello World</div> <div id="c"></div> </div> </body> <script> const $ = (e) => document.getElementById(e) const a = $('a') const b = $('b') const c = $('c') console.log(a.childNodes.length) // 5 console.log(b.childNodes.length) // 1 console.log(c.childNodes.length) // 0 console.log(a.hasChildNodes()) // true console.log(b.hasChildNodes()) // true console.log(c.hasChildNodes()) // false </script>
ownerDocument
- ownerDocument 属性,该属性会以 Document 对象的形式返回节点的 owner document。
- 在 HTML 中元素的 ownerDocument 属性始终是 HTML 文档本身
操作节点
- 上述所有的属性、方法都是只读的,DOM 另外提供了专门操作节点的方法。
appendChild ()
- appendChild 方法用于在 childNodes 列表的末尾处添加节点。添加的节点会更新相关的关系。
- 如果操作的节点是与存在的节点,那么这个节点会从之前的位置被转移到新的位置,一个节点不会再文档中同时出现两个或多个地方。
- 如果
-
<body> <span id="c"> World </span> <div id="a"><span id="b"> Hello </span></div> </body> <script> const $ = (e) => document.getElementById(e) const a = $('a') const b = $('b') const c = $('c') a.appendChild(c) console.log(a.lastChild) // <span id="c"> World </span> </script>
insertBefore ()
- 如果想把节点放到特定的位置可以使用 insertBefore 方法,该方法接收两个参数:需要插入的节点和参照节点。
- 调用该方法后需要插入的节点,会插入到参照节点的前面。成为参照节点的前一个同胞节点。
-
<body> <span id="d"> d </span> <div id="a"><span id="b"> b </span><span id="c"> c </span></div> </body> <script> const $ = (e) => document.getElementById(e) const a = $('a') const b = $('b') const c = $('c') const d = $('d') a.insertBefore(d, null) console.log(a.lastChild) // <span id="d"> d </span> a.insertBefore(d, a.firstChild) console.log(a.firstChild) // <span id="d"> d </span> a.insertBefore(d, a.lastChild) console.log(a.childNodes[a.childNodes.length - 2]) // <span id="d"> d </span> </script>
replaceChild ()
- 前两种方法并不会删除任何已有节点,而 replaceChild 方法会用插入的节点,替换并返回目标节点。
- replaceChild 方法接收两个参数:需要插入的节点和被替换的节点。使用该方法插入一个节点后,目标节点的所有关系指针都会被替换的节点复制过来。虽然被替换节点仍被同一文档拥有,文档中已经没有了他的位置。
-
<body> <span id="flower"> flower </span> <div id="a"><span id="life"> life </span><span id="love"> love </span></div> </body> <script> const $ = (e) => document.getElementById(e) const a = $('a') const life = $('life') const flower = $('flower') const love = $('love') const d1 = a.replaceChild(flower, a.firstChild) console.log(a.firstChild) // <span id="flower"> flower </span> console.log(d1) // <span id="life"> life </span> a.replaceChild(d1, a.firstChild) const d2 = a.replaceChild(flower, a.lastChild) console.log(a.lastChild) // <span id="flower"> flower </span> console.log(d2) // <span id="love"> love </span> </script>
removeChild ()
- 当我们想移除节点而不是替换节点的时候可以使用 removeChild 方法。该方法只接收一个参数:需要移除的节点。移除节点的同时也会返回被移除节点。
- 和上面的 replaceChild 方法一样,通过 removeChild 方法移除的节点,仍被当前文档所拥有,但是文档中已经没了改节点的位置。
-
<body> <div id="a"><span id="life"> life </span><span id="flower"> flower </span><span id="love"> love </span></div> </body> <script> const $ = (e) => document.getElementById(e) const a = $('a') const life = $('life') const flower = $('flower') const love = $('love') const d1 = a.removeChild(a.firstChild) console.log(a.firstChild) // <span id="flower"> flower </span> console.log(d1) // <span id="life"> life </span> a.insertBefore(d1, a.firstChild) const d2 = a.removeChild(a.lastChild) console.log(a.lastChild) // <span id="flower"> flower </span> console.log(d2) // <span id="love"> love </span> </script>
上述四种方法都用于操作某个父节点的子节点。如果想要使用他们就要先获取父节点。而如果操作的节点没有子节点或者不支持子节点,那么会导致程序抛出错误。
所有的节点类型共享了两个方法:cloneNode 和 normalize
cloneNode ()
- cloneNode 方法会返回与调用该方法节点,一模一样的节点。
- cloneNode 方法接收一个参数为布尔值,表示是否开启深复制。当传入 true 时会进行深度复制,即复制节点及其整个子DOM树。如果传入的是 false,那么只会复制调用该方法的节点。
- 复制到的节点属于当前文档,但是并未指定父节点,所以可以称之为孤儿节点。可以使用上述的 appendChild 、insertBefore 、replaceChild 方法将孤儿节点添加到文档当中去。
-
<body> <ul id="a"> <li>a</li> <li>b</li> <li>c</li> </ul> </body> <script> const $ = (e) => document.getElementById(e) const a = $('a') const b = a.cloneNode(true) console.log(b) //<ul id="a"> // <li>a</li> // <li>b</li> // <li>c</li> //</ul> const c = a.cloneNode(false) console.log(c) // <ul id="a"></ul> </script>
normalize ()
- 该方法会将当前节点和他的后代节点规范化。规范化后的 DOM 树中将不会出现空的文本节点。如有两个文本节点相邻那么会被合并为一个文本节点。
-
<body> <span id="a"> hello world </span> </body> <script> const $ = (e) => document.getElementById(e) const a = $('a') const b = a.childNodes[0].cloneNode() a.insertBefore(b, a.childNodes[0]) console.log(a.childNodes) // NodeList(2) [text, text] a.normalize() console.log(a.childNodes) // NodeList [text] </script>