js中dom节点操作
——基础不牢,地动山摇。本文来巩固一些原生的dom操作,在此之前先明确一下html的构成。
html 组成
html整个文档是由节点(Node)组成的,那么节点(Node)到底是什么呢,看mdn给出的定义。
Node
Node
是一个接口,各种类型的 DOM API 对象会从这个接口继承。它允许我们使用相似的方式对待这些不同类型的对象;比如,继承同一组方法,或者用同样的方式测试。
大概有这11种类型
常量 | 值 | 描述 |
---|---|---|
Node.ELEMENT_NODE | 1 | 一个 元素 节点,例如 <p> 和 <div> 。 |
Node.ATTRIBUTE_NODE | 2 | 元素 的耦合 属性 。 |
Node.TEXT_NODE | 3 | Element 或者 Attr 中实际的 文字 |
Node.CDATA_SECTION_NODE | 4 | 一个 CDATASection ,例如 <!CDATA[[ … ]]> 。 |
Node.PROCESSING_INSTRUCTION_NODE | 7 | 一个用于 XML 文档的 ProcessingInstruction (en-US) ,例如 <?xml-stylesheet ... ?> 声明。 |
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 节点 |
<ul class="ul" id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
//对于这样一个html片段来说。如果利用Node.childNodes,获取到的结果如下有五个li,和六个空文本节点组成
所以我们的html 基本都是由这些node节点组成。主要有element元素节点(nodeType为1),文本节点(nodeType为3),注释等等(nodeType为8)。我们详细看一下element元素节点
element元素节点
Element
是一个通用性非常强的基类
*所有属性继承自它的祖先接口 Node
,并且扩展了 Node
的父接口 EventTarget
,
通俗来说就是div
,ul
,li
这些标签就叫元素节点(element),我们通常所说的操作dom,节点大多数就是在此类上,调用对应的方法。根据mdn文档中所述,element继承自node的(element本身是就是node的一种类型)。
那么我们继续追根溯源,Node从其父类型 EventTarget
[1] 继承属性.
EventTarget
接口由可以接收事件、并且可以创建侦听器的对象实现。换句话说,任何事件目标都会实现与该接口有关的这三个方法。
简单来说EventTarget
提供了时间监听器addEventListener等三个方法处理事件,这就解释了所有的element元素都可以调用addEventListener方法。
Node 与 Element 关系
到此我们捋一下他们之间的关系
做完这些铺垫,可以正式进入操作dom了。更多其他的操作可以直接去mdn找Element实例对应的方法。
增加
1.Node.appendChild()
该方法将一个节点附加到指定父节点的子节点列表的末尾处。如果将被插入的节点已经存在于当前文档的文档树中,那么 appendChild()
只会将它从原先的位置移动到新的位置(不需要事先移除要移动的节点)。
2.Element.append()
该方法在 Element的最后一个子节点之后插入一组 Node 对象或 DOMString 对象(就是字符串)。被插入的 DOMString 对象等价为 Text 节点。
与 Node.appendChild() 的差异:
Element.append()
允许追加DOMString
对象,而Node.appendChild()
只接受Node
对象。Element.append()
没有返回值,而Node.appendChild()
返回追加的Node
对象。Element.append()
可以追加多个节点和字符串,而Node.appendChild()
只能追加一个节点。
主要就是插入节点数量的多少,还有就是二者一个定义在node类中,一个定义在element类中。
3.Node.insertBefore()
该方法在参考节点之前插入一个拥有指定父节点的子节点。如果给定的子节点是对文档中现有节点的引用,insertBefore()
会将其从当前位置移动到新位置(在将节点附加到其他节点之前,不需要从其父节点删除该节点)
1.appendChild
<ul class="ul" id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<!-- <li>5</li> -->
</ul>
let li = document.createElement('li');
li.innerHTML = 5
let ul = document.querySelector('ul');
ul.appendChild(li)
2.append
let li1 = document.createElement('li');
let li2 = document.createElement('li')
li1.innerHTML = 5
li2.innerHTML = 6
let ul = document.querySelector('ul');
ul.append(li1,li2)
3.insetBefore
let li = document.createElement('li');
li.innerHTML = 0
let ul = document.querySelector('ul');
let li1 = ul.firstChild;
ul.insertBefore(li,li1)
删除
Element.remove()
该方法,把对象从它所属的 DOM 树中删除。
Node.removeChild()
该方法从 DOM 中删除一个子节点。返回删除的节点。
注意
child
是要移除的那个子节点。node
是child
的父节点。oldChild
保存对删除的子节点的引用。oldChild
===child
. 如果保存了引用js垃圾回收将不会清理内存
用法
//删除第一个li
<ul class="ul" id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<!-- <li>5</li> -->
</ul>
1.remove
let ul = document.querySelector('ul');
let li1 = ul.firstElementChild;
li1.remove()
2.removeChild()
let ul = document.querySelector('ul');
let li1 = ul.firstElementChild;
ul.removeChild(li1)
结果如下
修改
1.Node.replaceChild()
该方法用指定的节点替换当前节点的一个子节点,并返回被替换掉的节点。
2.Element.replaceChildren()
该方法将一个 Node
的后代替换为指定的后代集合。这些新的后代可以为 DOMString
或 Node
对象。
Element.replaceChildren(...nodesOrDOMStrings) // 返回 undefined
parentNode.replaceChild(newChild, oldChild);
查找
如果要找某个具体节点直接利用document.querySelector(),去选择就好。这里只提供寻找父子节点。
父
1.Node.parentNode
该属性返回指定的节点在 DOM 树中的父节点。
parentNode
是指定节点的父节点。一个元素节点的父节点可能是一个元素 (Element
) 节点,也可能是一个文档 (Document
) 节点,或者是个文档碎片 (DocumentFragment
) 节点。
2.Node.parentElement
该属性返回指定的节点在 DOM 树中的父元素节点。
子
1.Element.children
是一个只读属性,返回 一个 Node 的子elements
,是一个动态更新的 HTMLCollection
(一个可迭代的对象)。
2.Node.childNodes
返回包含指定节点的子节点(Node
)的集合,该集合为即时更新的集合(live collection)。
测试小例子
🌰1 翻转内部节点
<ul class="ul" id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
将内部节点翻转。
思路1.获取内部节点,翻转之后放回去。
let div = document.querySelector('div');
let child = [...ul.children]; //这里返回的是类数组,需要拓展一下。
for(let i = 0,j=child.length-1;i<j;i++,j--){
[child[i],child[j]] = [child[j],child[i]]
}
ul.replaceChildren(...child)
思路2.利用appendChild的特性去修改,如果添加的是本身的元素就会将该元素移动到最后面。
let div = document.querySelector('div');
let child = [...ul.children];
for(let i =child.length-1;i>=0;i--){
ul.appendChild(child[i])
}
结果
🌰2 遍历element节点
<div id="content">
<div id="level-1.1">
<div id="level-2.1">
<div id="level-3.1"></div>
</div>
</div>
<div id="level-1.2">
<div id="level-2.2">
<div id="level-3.2"></div>
</div>
</div>
<div id="level-1.3">
<div id="level-2.3">
</div>
</div>
</div>
//我们把读取到ID作为遍历到该节点。
思路1.dfs深度优先,递归实现
let entry = document.querySelector('#content');
const dfs=(input)=>{
console.log('dfs,start')
let id= input.id
console.log(id)
let children = [...input.children];
if(children.length == 0){
return
}
for(let i= 0;i<children.length;i++){
dfs(children[i])
}
console.log('dfs,end')
}
dfs(entry)
思路2.bfs广度优先,采用辅助队列实现
let entry = document.querySelector('#content');
const bfs = (input)=>{
console.log('bfs,start')
let id = input.id;
let queue = [input];
while(queue.length){
let element = queue.shift();
console.log(element.id);
let children = [...element.children];
for(let i = 0;i<children.length;i++){
queue.push(children[i])
}
}
console.log('bfs,end')
}
bfs(entry)
转载自:https://juejin.cn/post/7178404733793599545