DOM
如今 Vue 和 React 框架应该广泛,封装了 DOM。但 DOM 操作一直都是前端工程师的基础,必备知识。只会 Vue 不懂 DOM 操作的前端程序员不会长久。
题目
- DOM 属于那种数据结构?
- DOM 操作常用的 API 有哪些?
- attr 和 property 的区别?
- 如果一次性插入多个 DOM 节点,考虑性能?
知识点
- DOM 本质
- DOM 节点操作
- DOM 结构操作
- DOM 性能
DOM 的本质
DOM( Document Object Model ) 的本质就是从 HTML 文件中解析构件出来的树。
DOM (Document Object Model) 译为文档对象模型,是 HTML 和 XML 文档的编程接口。
HTML DOM 定义了访问和操作 HTML 文档的标准方法。
DOM 以树结构表达 HTML 文档。
DOM 节点操作
获取 DOM 节点
// 通过 ID 获取
const div1 = document.getElementById('div1')
console.log('div1', div1)
// 通过 Tag Name 获取
const divList = document.getElementsByTagName('div') // 集合
console.log('divList.length', divList.length)
console.log('divList[1]', divList[1])
// 通过 Class Name 获取
const containerList = document.getElementsByClassName('container') // 集合
console.log('containerList.length', containerList.length)
console.log('containerList[1]', containerList[1])
// 通过 CSS 选择器来获取
const pList = document.querySelectorAll('p')
console.log('pList', pList)
const p1 = pList[0]
节点中的 property
// 获取节点
const pList = document.querySelectorAll('p')
const p = pList[0]
console.log(p.style.width) // 获取样式
p.style.width = '100px' // 修改样式
console.log(p.className) // 获取 class
p.className = 'p1' // 修改 class
// 获取 nodeName 和 nodeType
console.log(p.nodeName)
console.log(p.nodeType)
property 修改的是 js 变量(DOM节点对象)的属性
节点中的 attribute
// 获取节点
const pList = document.querySelectorAll('p')
const p = pList[0]
p.setAttribute('data-name', 'my-data')
console.log( p.getAttribute('data-name') ) // my-data
p.setAttribute('style', 'font-size:30px;')
console.log( p.getAttribute('style') ) // font-size:30px;
所谓的 attribute 是直接作用到标签上面的属性
property 和 attribute 的区别
- property:修改对象属性,不会体现到 html 结构中
- attribute:修改 html 属性,会改变 html 结构
- 然而两者都可能引起 DOM 重新渲染(一般情况下尽量用 property 操作)
DOM 结构操作
- 新增插入节点
- 获取子元素,获取父元素
- 删除子节点
下面 Demo 的 HTML 结构
<body>
<div id="div1" class="container">
<p id="p1">一段文字 1</p>
<p>一段文字 2</p>
<p>一段文字 3</p>
</div>
<div id="div2">
<img src="https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png"/>
</div>
<ul id="list">
</ul>
</body>
新增插入节点
// 获取 DOM 元素
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is newP'
// 插入节点
div1.appendChild(newP)
// 移动节点
const p1 = document.getElementById('p1')
div2.appendChild(p1)
获取子元素,获取父元素
// 获取父元素
const div1 = document.getElementById('div1')
const parent = div1.parentNode
// 获取子元素列表
const div1 = document.getElementById('div1')
const child = div1.childNodes
// 但是
console.log(child) // NodeList(7) [ #text, p#p1, #text, p, #text, p, #text ]
// 打印结果显示有 7 个元素,然而实际上 div1 下只有 3 个 p 标签
// #text 的为文本元素
// text 元素 和 p 元素的 nodeType 不同
console.log(child[0].nodeType) // #text 显示为 3
console.log(child[1].nodeType) // p 显示为 1
// 把 nodeList 变成数组,然后通过 filter 函数过滤 p 标签
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {
if (child.nodeType === 1) {
return true
}
return false
})
console.log('div1ChildNodesP', div1ChildNodesP) // [ p#p1, p, p ]
删除子节点
const div1 = document.getElementById('div1')
const child = div1.childNodes
div1.removeChild( child[0] )
DOM 性能
- DOM操作非常 “昂贵”(耗费性能),应避免频繁的 DOM 操作
- 可以对 DOM 做缓存,从而减少 DOM 操作
- 将频繁操作改为一次性操作
DOM 查询做缓存
// 不缓存 DOM 查询结果
for (let i = 0; i < document.getElementsByTagName('p').length; i++) {
// 每次循环都会重新计算 length , 频繁进行 DOM 查询!!!
}
// 缓存 DOM 查询结果
const pList = document.getElementsByTagName('p')
const length = pList.length
for (let i = 0; i < length; i++) {
// 缓存 length ,只进行一次 DOM 查询 :)
}
将频繁操作改为一次性操作
const list = document.getElementById('list')
// 创建一个文档片段,此时还没有插入到 DOM 结构中
const frag = document.createDocumentFragment()
for (let i = 0; i < 20; i++) {
const li = document.createElement('li')
li.innerHTML = `List item ${i}`
// 先插入文档片段中
frag.appendChild(li)
}
// 都完成之后,再统一插入到 DOM 结构中
list.appendChild(frag)
总结
DOM 本质就是从 HTML 文件中解析构件出来的树结构。常用 API 有节点操作、结构操作、attribute(修改 html 属性) 和 property(修改对象属性),一般尽量使用 property。性能方面应该尽可能减少不必要的 DOM 操作,可以做查询缓存或合并操作。