前言
本篇文章以介绍常见的DOM节点知识、DOM元素操作方法为目的,其中也对一些比较容易忽略的问题进行简要说明。才疏学浅,如有纰漏之处或建议欢迎留下评论。
Node节点
首先,简单看看Node节点。有三个属性个人认为比较需要注意,nodeType、nodeName、nodeValue。
- nodeType——节点类型
返回的是一个整数,表面节点的类型。包括元素节点(1)、文本节点(3)、注释节点(8)等。详见Node.nodeType - nodeName——节点名称
返回节点名称。
元素节点的 nodeName 与标签名相同
文本节点的 nodeName 始终是 #text
文档节点的 nodeName 始终是 #document
详见Node.nodeName - nodeValue——节点值
元素节点的 nodeValue 是 null
文本节点的 nodeValue 是文本本身
详见Node.nodeValue
Node节点间的关系
这个图是来自《Javascript高级程序设计》一书中的Node节点间的关系图谱,比较清晰地介绍了节点之间的关系。
- parentNode: 父节点
- childNodes: 所有子节点
- firstNode: 第一个子节点
- lastNode: 最后一个子节点
- previousSibling: 前一个兄弟节点
- nextSibling: 下一个兄弟节点
特别注意上述属性获取的并不只是元素节点,也会包含文本节点等。所以进行操作时需要进行元素类型判断过滤。
此外,还有一些方式可以获得相关的元素节点。
- Node.parentElement
- ParentNode.children(IE6-8返回的元素可能会包含注释节点)
- ParentNode.childElemnetCount
- ParentNode.firstElementChild
- ParentNode.lastElementChild
- NonDocumentTypeChildNode.previousElementSibling
- NonDocumentTypeChildNode.nextElementSibling
DOM元素获取方法
方法 | 简述 | 兼容性 |
---|---|---|
getElementById('id') | 通过id获取 | - |
getElementsByTagName('p') | 通过标签名获取 | - |
getElementsByClassName('class') | 通过class获取 | IE>= 9 |
getElementsByName('name') | 通过name属性获取 | - |
querySelector() | 返回匹配选择器的第一个元素 | IE >= 8 |
querySelectorAll() | 返回匹配选择器的所有元素 | IE >=8 |
特别注意:querySelectorAll()与其他方法获取的DOM元素是不同的,它返回的是静态的
NodeList 对象,其他返回的是动态的 HTMLCollection 对象。静态意味着不会随着DOM结构的变换而改变。举例如下:
// html
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
//js
let list = document.getElementById('list'),
child1 = document.getElementsByTagName('li'),
child2 = document.querySelectorAll('li')
console.log(child1.length) // 6
console.log(child2.length) // 6
let ele = document.createElement('li')
ele.innerHTML = 7
list.appendChild(ele)
console.log(child1.length) // 7
console.log(child2.length) // 6
所以,在使用getElementsByTagName、getElementsByClassName、getElementsByName方法时要特别注意循环处理DOM节点的情况。
创建DOM节点
createElement() 创建一个元素节点
createTextNode() 创建一个文本节点
createAttribute() 创建一个属性节点(用setAttribute方法更加方便)
createDocumentFragment() 创建一个文档片段(适合在批量操作DOM元素时使用,详见后面章节的例子)
DOM元素内容属性获取
-
元素内容的获取
这里有几个容易混淆的属性,innerHTML、outerHTML、innerText、outerText、textContent,都是可以获取元素内容。区别如下:属性 描述 兼容性 innerHTML 返回HTML文本,存在XSS攻击的问题。 outerHTML 返回内容包含元素及其后代的HTML文本。 textContent 返回元素所有文本内容,包括隐藏元素的文本,包括<style>、<script>不会返回HTML文本,避免直接设置HTML文本。 IE9+ innerText 返回文本内容,受CSS样式影响,会触发DOM重排,不包括隐藏元素的文本,不包括<style>、<script>,避免直接设置HTML文本。 outerText 非标准属性。获取时返回与innerText相同内容,设置时删除当前节点替换为给定文本。 -
元素属性
Element.attributes(): 引用MDN官网的描述返回该元素所有属性节点的一个实时集合。该集合是一个 NamedNodeMap 对象,不是一个数组,所以它没有数组的方法,其包含的属性节点的索引顺序随浏览器不同而不同。更确切地说,attributes 是字符串形式的名(name)/值(value)对,每一对名/值对对应一个属性节点。
ele.getAttribute(attributeName) 获取属性
ele.setAttribute(name, value) 设置属性
HTMLElement.dataset: 获取data-*属性集 -
元素样式
HTMLElement.style 返回元素的内联样式(没错,样式表的属性会被忽略)
单个样式的设置:ele.style.color='#000'
多个样式的设置:- 依次设置
ele.style.cssText='color: blue'
ele.setAttribute('style', 'color: blue')
获取元素样式信息:
window.getComputedStyle(ele).color
-
window.getComputedStyle(ele).getPropertyValue('color')
getComputedStyle方法接受第二个参数为伪元素,如'::after'
。关于getComputedStyle详细介绍可以看看张鑫旭大神的获取元素CSS值之getComputedStyle方法一文。
- 元素类名
className 获取或设置元素的类名。
classList 只读,返回元素的类属性的实时 DOMTokenList集合。但可以使用 add() 和 remove() 方法修改。也有类似jQuery的toggle方法,但是兼容性较差。
插入DOM元素
方法 | 简述 |
---|---|
node.appendChild(newNode) | 向node节点插入一个新节点newNode |
node.insertBefore(newNode, tarNode) | 在node节点的tarNode子节点前插入一个新节点newNode |
node.replaceChild(newNode, tarNode) | 替换node节点的tarNode子节点为新节点newNode |
node.removeChild(tarNode) | 移除node节点的tarNode子节点 |
node.cloneNode(flag) | 复制节点,flag: true 深复制;flag: false 浅复制 |
此处深复制为复制节点及其整个子树,浅复制则仅复制节点本身。
DOM操作的性能问题
频繁进行DOM操作其实会造成多次重排Reflow,影响性能。举个常见的例子,在id为container的元素中添加5个按钮,每个按钮的文案是相应序号,点击打印输出对应序号。解决办法有以下几种:
-
依次创建button元素,使用appenChild添加到列表中
当然,这个方法是最不推荐的,因为多次对DOM进行操作,会造成多次页面重排,性能太差。let container = document.getElementById('container') for(let i = 1; i <= 5; i++) { let btn = document.createElement('button'), text = document.createTextNode(i) btn.appendChild(text) btn.addEventListener('click', () => { console.log(i) }) container.appendChild(btn) }
-
利用DocumentFragment
引用MDN官网关于DocumentFragment的介绍:DocumentFragment 接口表示一个没有父级文件的最小文档对象。它被当做一个轻量版本的 Document 使用,用于存储已排好版的或尚未打理好格式的XML片段。最大的区别是因为DocumentFragment不是真实DOM树的其中一部分,它的变化不会引起DOM树的重新渲染的操作(reflow) ,或者导致性能影响的问题出现。
没错,利用DocumentFragment我们能够避免方法1中多次操作DOM的问题,性能得到提升。
let container = document.getElementById('container'), fragment = document.createDocumentFragment() for(let i = 1; i <= 5; i++) { let btn = document.createElement('button'), text = document.createTextNode(i) btn.appendChild(text) btn.addEventListener('click', () => { console.log(i) }) fragment.appendChild(btn) } container.appendChild(fragment)
-
利用字符串拼接
使用字符串拼接的方法插入DOM元素是效率最高的。并且,这里将事件绑定到了父元素上,一方面可以使用动态添加元素的事件,另一方面当需要在大量元素上绑定事件时,这种方法更加优雅并且节省内存。let container = document.getElementById('container'), btns = ''; for(let i = 1; i <= 5; i++) { let btn = `<button class="btn_num">${i}</button>` btns += btn } container.addEventListener('click', (event) => { let target = event.target if(target.className == 'btn_num') { console.log(target.innerHTML) } }) container.innerHTML = btns
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。