大家好,我是一碗周,一个不想被喝(内卷)的前端。如果写的文章有幸可以得到你的青睐,万分有幸~
这是【从头学前端】系列文章的第四十二篇-《Node对象》
概述
Node对象是什么
DOM的标准规范中提供了Node对象,该对象主要提供了了用于解析DOM节点树结构的属性和方法。
DOM树结构主要是依靠节点进行解析,称为DOM节点树结构。Node对象是解析DOM节点树结构的主要入口。
Node对象提供的属性和方法,可以实现遍历节点、插入点和替换节点等操作。
Node对象并不像Document对象一样,提供可以直接使用的对象,而且不允许通过new关键字进行创建对象。
我们之前定位的的页面元素其实就是Node对象,称为节点元素;以及自己创建的节点,也是一个Node对象。
示例代码如下:
<body>
<button id="btn">按钮</button>
<script>
var btn = document.getElementById('btn')
//定位的的页面元素其实就是Node对象,称为节点元素
console.log(btn instanceof NOde)//true
//自己创建的节点也是Node对象
var button = document.createElement('button')
console.log(button instanceof Node)//true
//获取其全部属性and方法
console.log(Node.prototype)
</script>
</body>
继承链关系
Node对象是继承于EventTarget对象的,EventTarget是一个用于接收事件的对象。
DOM的标准规范中的Document对象和Element对象都是继承于Node对象的。
测试代码如下
<body>
<script>
console.log(Node.prototype instanceof EventTarget)//true
console.log(Document.prototype instanceof Node)//true
console.log(Element.prototype instanceof Node)//true
</script>
</body>
判断节点类型
Node 对象中提供了 nodeName
(只读), nodeType
(只读), nodeValue
分别可以用于获取指定节点的节点名称、节点类型、和节点的值。
DOM 节点树结构中,我们实际开发最常见的节点有:
节点名称 | 含义 | 对应的nodeType 值 |
---|---|---|
元素节点 | 表示 HTML 页面中的标签(即 HTML 页面的结构) | 1 |
文本节点 | 表示 HTML 页面中的标签所包含的文本内容 | 3 |
属性节点 | 表示 HTML 页面中的开始标签包含的属性 | 2 |
示例代码如下所示:
HTML结构如下:
<button class="cls" id="btn">按钮</button>
元素节点
var btnElement = document.getElementById('btn')
console.log(btnElement.nodeName) // BUTTON
console.log(btnElement.nodeType) // 1
console.log(btnElement.nodeValue) // null
元素节点的 nodeName
属性值为标签名称(大写)
文本节点
由 DOM 结构树得知文本节点属于元素节点的子节点
我们可以使用 Node.firstChild
属性(只读)来获取其子节点,如果节点是无子节点,则返回 null。
var textNode = btnElement.firstChild
console.log(textNode.nodeName) // #text
console.log(textNode.nodeType) // 2
console.log(textNode.nodeValue) // 按钮
文本节点的
nodeName
属性值是固定值,为#text
nodeValue
并不是一个只读属性,也就是说我们不仅可以获取,而且可以做修改,示例代码如下
textNode.nodeValue = '新按钮'
属性节点
属性节点并不是一个子级节点,也不是兄弟节点,我们获取属性节点可以使用 Element.getAttributeNode()
方法
作用:返回指定元素的指定属性节点。
语法结构如下:
var attrNode = element.getAttributeNode(attrName);
attrName
:一个包含属性名称的 字符串
var attrNode = btnElement.getAttributeNode('class')
console.log(attrNode.nodeName) // class
console.log(attrNode.nodeType) // 2
console.log(attrNode.nodeValue) // cls
属性节点的nodeName
为当前元素的属性名称
属性节点的nodeValue
属性值为当前属性名称对应的值,当然可以做修改
遍历节点
这里所谓的遍历节点就是指:根据节点树结构来获取节点。由节点树我们得知,节点树中分三层关系,分别是祖先与后代、父级与子级、兄弟关系,祖先与后代可以用父级与子级代替的。所以我们这里学习的是如何获取父节点、子节点、相邻兄弟节点
获取父节点
通过HTML 页面中指定元素查找其父节点,我们可以使用 Node 对象的parentNode
属性实现,其语法结构如下
var parentNode = node.parentNode
parentNode
:指定节点的父节点,一个元素节点的父节点可能是一个元素Element
节点,也可能是一个文档Document
节点
示例代码如下:
<body>
<ul id="parent">
<li>苹果</li>
<li id="mi">小米</li>
<li>华为</li>
</ul>
<script>
//作为子节点
var mi = document.getElementById('mi')
//通过子节点获取父节点
var parent1 = mi.parentNode
console.log(parent1)// ul
</script>
</body>
与parentNode
属性类似的还存在一个parentElement
属性,这个属性返回当前节点的父元素节点,如果该元素没有父节点,或者父节点不是一个 DOM 元素,则返回null
。
两者操作<html>
以下的节点并没有什么区别,但是如果操作<html>
标签,就会出现区别,如下代码展示
<body>
<ul id="parent">
<li>苹果</li>
<li id="mi">小米</li>
<li>华为</li>
</ul>
<script>
//<html>作为子节点
var html = document.documentElement
//通过子节点获取父节点
console.log(html.parentNode)//文档节点
console.log(html.parentElement)// null
</script>
</body>
获取子节点/空白节点问题
通过 HTML 页面中执行元素查找其子节点,我们可以通过以下 Node 对象的属性实现
属性名 | 描述 |
---|---|
Node.childNodes | 获取指定节点的所有子节点 |
Node.firstChild | 获取指定节点的第一个子节点 |
Node.lastChild | 获取指定节点的最后一个子节点 |
示例代码如下
<body>
<ul id="parent">
<li>苹果</li>
<li id="mi">小米</li>
<li>锤子</li>
</ul>
<script>
var parent = document.getElementById('parent')
// childNodes属性-获取指定节点的所有子节点
var children = parent.childNodes
console.log(children) // NodeList(7) [text, li, text, li#mi, text, li, text]
</script>
</body>
如果直接获取的话大多数浏览器都是存在问题的,因为我们只有三个文本节点,获取却得到了7个,而多出来的4个结果,而且这四个结果都是文本节点,这些没有内容的文本节点也就成为空白节点。
那这几个文本节点在什么地方呢,咱们在编写 HTML 代码的时候,为了增加可读性,我们增加了换行和空格,而这些空格和换行都属于文本内容。
空白节点问题图解如下
我们得知所有的空白节点都是文本节点,而文本节点的nodeType
的值为3,而元素节点的nodeType
的值为1;我们可以使用循环加判断的方式来解决这个问题。示例代码如下
<body>
<ul id="parent">
<li>苹果</li>
<li id="mi">小米</li>
<li>锤子</li>
</ul>
<script>
var parent = document.getElementById('parent');
// childNodes属性-获取指定节点的所有子节点
var children = parent.childNodes;
console.log(children);
//用于存取真正的字节集
var arr = []
for (var i = 0; i < children.length; i++) {
var child = children[i]
if (child.nodeType === 1) {
// Array.push()方法用于往数组追加数据
arr.push(child)
}
}
console.log(arr)// Array(3)
</script>
</body>
获取相邻兄弟节点
通过 HTML 页面中指定元素查找相邻兄弟节点,我们可以通过以下 Node 对象的属性实现:
属性名 | 描述 |
---|---|
Node.nextSibling (只读) | 返回当前节点的后一个兄弟节点,没有则返回null. |
Node.previousSibling (只读) | 返回当前节点的前一个兄弟节点,没有则返回null. |
示例代码如下
<body>
<ul id="parent">
<li>苹果</li>
<li id="mi">小米</li>
<li>锤子</li>
</ul>
<script>
var parent = document.getElementById('parent');
// childNodes属性-获取指定节点的所有子节点
var children = parent.childNodes;
console.log(children);
//用于存取真正的字节集
var arr = []
for (var i = 0; i < children.length; i++) {
var child = children[i]
if (child.nodeType === 1) {
// Array.push()方法用于往数组追加数据
arr.push(child)
}
}
console.log(arr)// Array(3)
</script>
</body>
插入节点
Node.appendChild()方法
Node.appendChild()
方法将一个节点附加到指定父节点的子节点列表的末尾处。如果将被插入的节点已经存在于当前文档的文档树中,那么appendChild()
只会将它从原先的位置移动到新的位置(不需要事先移除要移动的节点)。
语法结构如下
element.appendChild(aChild)
aChild
:要追加给父节点的节点。
该方法的返回值就是该子节点。
示例代码如下:
<body>
<ul id="parent">
<li>苹果</li>
<li id="mi">小米</li>
<li>锤子</li>
</ul>
<script>
var parent = document.getElementById('parent')
//创建一个子节点
var newLi = document.createElement('li')
//创建文本节点
var textNode = document.createTextNode('华为')
//将文本节点追加到新的子节点中
newLi.appendChild(textNode)
//将新的子节点追加到父级节点中
parent.appendChild(newLi)
</script>
</body>
Node.insertBefore()方法
Node.insertBefore()
方法在参考节点之前插入一个拥有指定父节点的子节点。如果给定的子节点是对文档中现有节点的引用,insertBefore()
会将其从当前位置移动到新位置。如果引用节点为null
,则将指定的节点添加到指定父节点的子节点列表的末尾。
语法结构如下
var insertedNode = parentNode.insertBefore(newNode, referenceNode);
parentNode
:新插入节点的父节点newNode
:用于插入的节点referenceNode
:newNode
将要插在这个节点之前
返回值insertedNode
表示被插入节点(newNode
)
示例代码如下
<body>
<ul id="parent">
<li>苹果</li>
<li id="mi">小米</li>
<li>锤子</li>
</ul>
<script>
var parent = document.getElementById('parent')
//创建一个子节点
var newLi = document.createElement('li')
//创建文本节点
var textNode = document.createTextNode('华为')
//将文本节点追加到新的子节点中
newLi.appendChild(textNode)
//获取目标节点
var mi = document.getElementById('mi')
//将新建的子节点插入到小米之前
parent.insertBefore(newLi, mi)
//如果要插入的节点之前的那个节点为 null ,则默认插入到最后
var nli = document.createElement('li')
var tNode = document.createTextNode('三星')
nli.appendChild(tNode)
parent.insertBefore(nli, null)
</script>
</body>
删除节点
Node对象提供了Node.removeChild()
方法从DOM中删除一个子节点。返回删除的节点。
语法结构如下:
var oldChild = node.removeChild(child);
//OR
element.removeChild(child);
child
:要移除的那个子节点.node
:child
的父节点.
返回值oldChild
保存对删除的子节点的引用,所以说oldChild
和child
是相等的。
值得注意的是,我们删除之后子节点仍然存在于内存中,只是没有添加到当前文档的DOM树中,因此我们还可以把这个节点重新插入到文档中,但前提是用一个变量来存储被删除的返回值,才可以实现。如果不存储这个返回值,则认为被移除的节点已经是无用的,在短时间内将会被内存管理回收。
如果child节点不是node节点的子节点,则该方法会抛出异常。
示例代码如下
<body>
<ul id="parent">
<li>苹果</li>
<li id="mi">小米</li>
<li>锤子</li>
</ul>
<script>
var parent = document.getElementById('parent')
var mi = document.getElementById('mi')
var oldChild = parent.removeChild(mi)
</script>
</body>
替换节点
Node对象提供了replaceChild()
方法可以实现 HTML 页面中的节点替换功能。
语法结构如下
var replaceNode = parentNode.replaceChild(newChild, oldChild);
参数说明
parentNode
:表示oldChild
节点的父级节点。newChild
:用来替换oldChild
的新节点。如果该节点已经存在于DOM树中,则它首先会被从原始位置删除。oldChild
:被替换掉的原始节点。
返回值是被替换的节点,即 replaceNode == oldChild
。
示例代码如下所示:
<body>
<ul id="phone">
<li>华为</li>
<li id="mi">小米</li>
<li>中兴</li>
</ul>
<script>
// 1. 获取目标节点的父节点
var parent = document.getElementById('phone')
// 2. 获取目标节点
var mi = document.getElementById('mi')
// 3. 创建节点
var newLi = document.createElement('li')
var nodeText = document.createTextNode('黑鲨')
newLi.appendChild(nodeText)
// 4..进行替换
parent.replaceChild(newLi, mi)
</script>
</body>
执行结果如下所示:
这里还存在着另外一种情况,就是当前节点已经存在,这里进行替换的话,新节点就会从原来的位置被删除了。
复制节点
Node对象提供了cloneNode()
方法实现HTML页面中节点复制的功能。
语法结构如下
var dupNode = node.cloneNode(deep);
node
:将要被克隆的节点dupNode
:克隆生成的副本节点deep
(可选):是否采用深度克隆,如果为true
,则该节点的所有后代节点也都会被克隆,如果为false
(默认值),则只克隆该节点本身.
示例代码如下
<body>
<button>按钮</button>
<script>
// 1. 获取需要复制的节点
var btn = document.getElementsByTagName('button')[0]
// 2. 复制节点操作
var newbtn = btn.cloneNode(true) // 如果为 false 则不复制子节点
// 3. 将子节点添加到页面中
var body = document.body
body.appendChild(newbtn)
</script>
</body>
值得注意的是,复制操作时,如果其节点有id属性,则会一起复制,但是id的属性是唯一的属性,这里就会出现问题的。
写在最后
你如果看到这里,我感到很荣幸,如果你喜欢这篇文章,你可以为这篇文章点上一个小赞;你如果喜欢这个专栏,我会一直更新到百篇以上,可以点一下后面的链接从头学前端 - 一碗周的专栏进入之后给个关注。
最后也可以给我点个关注,万分荣庆。
往期推荐