正文
一、Node 与 Element 的关系
Node
是一个接口(基类),本身继承自 EventTargent 接口,有许多接口都从 Node
继承方法和属性:Document
、Element
、Attr
、CharacterData
(which Text
、Comment
and CDATASection
inherit)ProcessingInstruction
、DocumentFragment
、DocumentType
、Notation
、Entity
、EntityReference
。
Element
继承于Node
,具有Node
的方法,同时又拓展了很多自己的特有方法。
比如以下这些方法,都明显区分了 Node
和 Element
。
Node | Element |
childNodes |
children |
parentNode 、parentElement |
|
nextSibling |
nextElementSibling |
previousSibling |
previousElementSibling |
... | ... |
我们常说的 DOM 节点就是指 Node
,而 DOM 元素是指 Element
。DOM 节点包括了 Element
、Document
、Comment
、Text
等。它们都有一个特定的节点类型(nodeType
)来表示,如下:
常量 | 值 | 描述 |
Node.ELEMENT_NODE |
1 |
一个元素节点,例如 <p> 和 <div> 。 |
Node.TEXT_NODE |
3 |
Element 或者 Attr 中实际的 文字 |
Node.COMMENT_NODE |
8 |
一个 Comment 节点。 |
Node.DOCUMENT_NODE |
9 |
一个 Document 节点。 |
Node.DOCUMENT_TYPE_NODE |
10 |
描述文档类型的 DocumentType 节点。例如 <!DOCTYPE html> 就是用于 HTML5 的。 |
Node.DOCUMENT_FRAGMENT_NODE |
11 |
一个 DocumentFragment 节点 |
还有一些是不常用或者已废弃的,这里没有列举出来,详见 Node.nodeType。
<div id="root">Root</div>
const element = document.getElementById('root') element.nodeType // 1,一个 Element 节点 element.children // HTMLCollection [] element.childNodes // NodeList [text] element.childNodes[0].nodeType // 3,一个 Text 节点 element.parentElement // Element body element.parentNode // Element body
简单总结一下:
Node
节点有很多种,比如Element
、Document
、Text
、Comment
等,而Element
节点只是其中一种而已。Element
是Node
的派生类,因此Element
节点可以访问Node
的属性和方法,反之不可。Element
节点常被称为“元素”。- 注意,常见的
document
对象是 与Element
不是同一类型的节点,因此称为Document
节点比较合适。
二、Node 常见属性
如下:
属性 | 读写 | 描述 |
Node.nodeType |
只读 | 返回节点类型,如上表。 |
Node.nodeName |
只读 | 返回节点名字的 DOMString。若是 Element 节点跟它管理的标签对应,如 'DIV' 、'SPAN' (一般都是大写的)。若是 Text 节点对应的是 '#text' 。若是 Document 节点对应是 '#document' 。(若是 Element 节点,有个类似的 Element.tagName 属性返回) |
Node.nodeValue |
可读写 | 返回或设置当前节点的值。一般用于 Text 、Comment 节点的文本内容。而像 Element 、Document 节点其返回值为 null 。若 nodeValue 的值为 null ,则对它赋值也不会有任何效果。 |
Node.parentNode |
只读 | 返回一个当前节点 Node 的父节点 。如果没有这样的节点,比如说像这个节点是树结构的顶端或者没有插入一棵树中, 这个属性返回 null 。 |
Node.childNodes |
只读 | 返回一个包含了该节点所有子节点的实时的NodeList 。NodeList 是动态变化的。如果该节点的子节点发生了变化,NodeList 对象就会自动更新。 |
三、添加节点
一般情况,往 DOM 中添加节点,会使用 Node.appendChild()
方法和 Element.append()
方法。它们的作用都是:将节点附加到指定父节点的子节点列表的末尾处。
但有些差异,如下:
Element.append()
方法接受Node
对象和DOMString
对象,而Node.appendChild()
只接受Node
对象。Element.append()
没有返回值,而Node.appendChild()
返回追加的 Node 对象。Element.append()
可以追加多个节点和字符串,而Node.appendChild()
只能追加一个节点。
Element.append()
接受字符串作为参数,其实是将字符串转化为Text
节点再附加。
举个例子:
<div id="root">Root</div>
const root = document.getElementById('root') const p = document.createElement('p') // 创建 p 元素 p.append('paragraph') // 往 p 元素添加 Text 节点 root.appendChild(p) // appendChild 每次只能添加一个 root.append('123', 'abc') // append 可以每次可添加多个
这时候,DOM 变成了:
<div id="root"> "Root" <p>paragraph</p> "123" "abc" </div>
注意点
Element.append()
参数若是非字符串的原始值,会先转换为字符串的。
root.append(true) // 成功添加一个值为 "true" 的文本节点 root.append(Symbol('desc')) // 将会抛出 TypeError,因为 Symbol 值无法转换为字符串
需要注意的是,
Symbol
原始值不能转换为字符串,只能将其转换成对应的包装对象,再调用Symbol.prototype.toString()
方法。
- 如果将被插入的节点已经存在于 DOM 中,那么
appendChild()
和append()
只会将它从原先的位置移动到新的位置。
<div id="modal">Modal</div> <div id="root">Root</div>
const root = document.getElementById('root') const modal = document.getElementById('modal') root.appendChild(modal) // 会发生什么呢?
DOM 将会变成这样:
<div id="root"> "Root" <div id="modal">Modal</div> </div>
注意,这种情况下原有节点的事件监听器也会随之移动。
Node.appendChild()
传入多个Node
节点不会报错,但仅第一个有效。
const root = document.getElementById('root') const span1 = document.createElement('span') span1.append('span1') const span2 = document.createElement('span') span2.append('span2') root.appendChild(span1, span2) // 仅 span1 节点添加成功 root.appendChild('string') // 但是这样会报错,TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
四、移除节点
移除节点,对应的方法是 Node.removeChild()
和 Element.remove()
。
Node.removeChild()
方法是从 DOM 中移除一个子节点,并返回删除的节点。该方法接受一个 Node 节点。Element.remove()
是移除一个节点,无返回值。
由于
Node.removeChild()
会返回被移除的子节点,所以该子节点仍存在于内存中,因此你可以把这个节点重新添加回 DOM 中。若没有对被移除节点保持引用,则认为它已经没有用了,短时间内将会被垃圾回收。
举个例子:
<div id="root"> <p id="p"> p 元素节点</p> <span id="span">span 元素节点</span> 文本节点 </div>
const root = document.getElementById('root') const p = document.getElementById('p') const span = document.getElementById('span') p.remove() // <p> 节点从 DOM 中删除 root.removeChild(span) // <div> 的子节点 <span> 被移除,不存在引用,因此很快 span 对象将会被回收掉。 // ️ 此时 root.childNodes 可不只有一个 Node 子节点,前面 HTML 的写法,会产生一些空白符的文本节点。 root.childNodes // NodeList(3) [text, text, text],而且最后一个才是 "\n 文本节点\n " 对应的文本节点
五、替换节点
Node.replaceChild()
方法用指定的节点替换当前节点的一个子节点,并返回被替换掉的节点。语法如下:
parentNode.replaceChild(newChild, oldChild)
请注意,第二个参数 oldChild 必须是 parentNode 节点下的子节点,否则会抛出异常:DOMException: Failed to execute 'replaceChild' on 'Node': The node to be replaced is not a child of this node.
举个例子:
<div id="modal">Modal</div> <div id="root">Root</div>
const root = document.getElementById('root') const modal = document.getElementById('modal') root.replaceChild(modal, root.childNodes[0]) // 将 modal 节点替换了 root 节点的第一个子节点(当然本示例中也只有一个子节点)
因此,DOM 变成了:
<div id="root"> <div id="modal">Modal</div> </div>
六、插入节点
插入节点,这里使用的时 Node.insertBefore() 方法。语法如下:
var insertedNode = parentNode.insertBefore(newNode, referenceNode)
请注意,若第一个参数
newNode
是 DOM 中某个节点的引用,使用Node.insertBefore()
会将其从原来的位置移动到新位置。这点也Node.appendChild()
一致。
举个例子:
<div id="root">Root</div>
const root = document.getElementById('root') // 新节点 const p = document.createElement('p') p.append('paragraph') // 将新节点插入到 root 第一个子节点的前面 root.insertBefore(p, root.childNodes[0])
因此,DOM 变成了:
<div id="root"> <p>paragraph</p> "Root" </div>
注意两种情况:
root.insertBefore(p) // 不会报错,也不会往 root 内插入任何节点 root.insertBefore(p, null) // 当引用节点为 null,p 将会被插入到 root 子节点列表末尾(类似 appendChild 的作用)
还记得以前项目里面,动态加载脚本,就是使用 insertBefore
插入到 DOM 中的。
const script = document.createElement('script') script.src = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js' script.onload = () => { /* do something, such as event listener */ } script.onerror = err => { /* handle error */ } const s0 = document.getElementsByTagName('script')[0] if (s0 && s0.parentNode) { s0.parentNode.insertBefore(script, s0) }
比如,动态加载微信 JS-SDK,然后在脚本加载完成调用 wx.config({ ... })
接口注入权限验证配置
先写那么多吧,后面有必要再补充其他 DOM API...
发现一篇文章:JavaScript 操作 DOM 常用的 API,里面非常全面,可移步前往。