这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天
DOM API
- DOM: Document Object Model
DOM API 是实现网页操作的 API (W3C 标准),包括 DOM 树的操作、事件、样式、位置等。VUE 和 REACT 封装了 DOM API,提供了更加方便的操作方式。
JS Web API
- DOM API: Document Object Model,操作 DOM 树、事件、样式、位置等
- BOM API: Browser Object Model,操作浏览器窗口、历史记录、位置等
- AJAX API: Asynchronous JavaScript and XML,异步请求
- Canvas API: 绘制图形
- Web Storage API: 本地存储
- 事件绑定 API: addEventListener、removeEventListener、dispatchEvent
DOM 的本质
DOM 本质要从 xml 说起,xml 是一种标记语言,用来描述数据结构。xml 的标签是可以嵌套的,比如:
<book>
<title>JavaScript 高级程序设计</title>
<author>尼古拉斯·赵四</author>
</book>
XML 的标签是可以自定义的,比如 <book>、<title>、<author> 等。
HTML 是一种特定的 xml,它的标签都是固定的,比如 <div>、<p> 等。DOM 就是用来描述 HTML 的数据结构,它是一个树形结构,每个节点都是一个对象,可以通过 JS 操作 DOM 树。
DOM 本质是一棵树,它是由 HTML 解析生成的树。
DOM 节点操作
获取 DOM 节点
- 通过 ID 获取:
document.getElementById(id)
const box = document.getElementById('box')
注意 id 是唯一的,如果有多个相同的 id,只会返回第一个。
- 通过标签名获取:
document.getElementsByTagName(tagName)
const divs = document.getElementsByTagName('div')
返回结果实际上是一个集合,可以通过下标获取,也可以通过 length 属性获取长度。
- 通过类名获取:
document.getElementsByClassName(className)
const box = document.getElementsByClassName('box')
- 通过 css 选择器获取:
document.querySelector(cssSelector)、document.querySelectorAll(cssSelector)
const box = document.querySelector('#box')
const divs = document.querySelectorAll('div')
property
property 是 DOM 节点的属性,比如 id、className、innerHTML 等。用于获取或设置节点的属性。
const box = document.getElementById('box')
box.id // 'box'
box.className // 'box'
box.innerHTML // '<p>hello</p>'
可以通过它来设置节点的属性。
box.id = 'box1'
box.className = 'box1'
box.innerHTML = '<p>world</p>'
当然也能改变样式。
box.style.color = 'red'
box.style.fontSize = '20px'
box.className = 'box1'
nodeName、nodeType、nodeValue 等属性也是 property。如:
const box = document.getElementById('box')
box.nodeName // 'DIV'
box.nodeType // 1,1 表示元素节点
box.nodeValue // null
attribute
attribute 是 DOM 节点的属性,比如 id、class、style 等。用于获取或设置节点的属性。
const box = document.getElementById('box')
box.getAttribute('id') // 'box'
box.getAttribute('class') // 'box'
box.getAttribute('style') // 'color: red; font-size: 20px;'
可以通过 setAttribute 来设置节点的属性。
box.setAttribute('id', 'box1')
box.setAttribute('data-name', 'box')
box.setAttribute('style', 'color: blue; font-size: 30px;')
总的来说,property 和 attribute 的区别是:property 用于修改对象属性,不会体现到 html 结构中,attribute 用于修改 html 属性,会改变 html 结构。例如:
<div id="box" class="box" style="color: red; font-size: 20px;"></div>
const box = document.getElementById('box')
box.id = 'box1' // 修改对象属性,不会改变 html 结构
box.className = 'box1' // 修改对象属性,不会改变 html 结构
box.style.color = 'blue' // 修改对象属性,不会改变 html 结构
box.style.fontSize = '30px' // 修改对象属性,不会改变 html 结构
box.setAttribute('id', 'box2') // 修改 html 属性,会改变 html 结构
box.setAttribute('class', 'box2') // 修改 html 属性,会改变 html 结构
property 是对已经存在的属性进行修改,attribute 可以添加新的属性。它们都会引起 DOM 的重新渲染。平常情况下,我们使用 property 更多一些。
DOM 结构操作
创建/插入/删除节点
- 创建节点:
document.createElement(tagName)
const div = document.createElement('div')
- 插入节点:
node.appendChild(childNode)、node.insertBefore(newNode, referenceNode)
const div = document.createElement('div')
const p = document.createElement('p')
p.innerHTML = 'hello'
div.appendChild(p)
const p1 = document.createElement('p')
p1.innerHTML = 'world'
div.insertBefore(p1, p) // 在 p 前面插入 p1
可以使用它来移动节点。
<div id="div1">
<p id="p1">hello</p>
<p>world</p>
</div>
<div id="div2"></div>
const div2 = document.getElementById('div2')
const p1 = document.getElementById('p1')
div2.appendChild(p1) // 将 p1 移动到 div2 中
注意,<p>Nihao</p> 中有一个文本节点 Nihao (nodeType为3),它是 p 的子节点。如果只想查找标签节点可以使用 filter 过滤。
const div = document.getElementById('div')
const p = div.childNodes.filter(node => node.nodeType === 1)
- 删除节点:
node.removeChild(childNode)
const div = document.getElementById('div')
const p = document.getElementById('p')
div.removeChild(p)
DOM 性能
DOM 操作非常昂贵,因为它会引起 DOM 的重新渲染。所以,我们应该尽量减少 DOM 操作。
DOM 查询做缓存
// 不缓存的 DOM 查询
for (let i=0;i< document.getElementsByTagName('p').length;i++) {
// 每次循环都会计算 document.getElementsByTagName('p').length
document.getElementsByTagName('p')[i].style.color = 'red'
}
// 缓存的 DOM 查询
const p = document.getElementsByTagName('p')
for (let i=0;i< p.length;i++) {
p[i].style.color = 'red'
}
将频繁操作改为一次性操作
Fragment 是一个临时的 DOM 节点,可以在内存中创建 DOM 节点,然后一次性插入 DOM 树。
const listNode = document.getElementById('list')
// 创建文档片段,此时还没有插入 DOM 树
const frag = document.createDocumentFragment()
for (let i=0;i< 10;i++) {
const li = document.createElement('li')
li.innerHTML = 'List item' + i
frag.appendChild(li) // 将 li 插入文档片段
}
listNode.appendChild(frag) // 都完成后,再将文档片段插入 DOM 树
实例
- DOM 是哪种数据结构?如何遍历 DOM 树?
DOM 是树形结构,可以使用递归遍历 DOM 树。
function traverseDOM(node, callback) {
if (node) {
callback(node)
node = node.firstChild
while (node) {
traverseDOM(node, callback)
node = node.nextSibling // 遍历下一个兄弟节点
}
}
}
property和attribute的区别?
property 是对已经存在的属性进行修改,不会体现到 html 结构中。attribute 可以添加新的属性,会改变 html 结构。
BOM API
- BOM API: Browser Object Model,操作浏览器窗口、历史记录、位置等
navigator
用于获取浏览器信息。
console.log(navigator.userAgent) // Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
console.log(navigator.platform) // Win32
chrome 浏览器中有 safari,这是因为为了兼容各个浏览器,chrome 也会模拟 safari 的行为。
screen
用于获取屏幕信息。
console.log(screen.width) // 1920
console.log(screen.height) // 1080
location
用于获取当前页面的 url 信息。
// http://localhost:8080/#/home
console.log(location.href) // http://localhost:8080/#/home
console.log(location.hash) // #/home,hash 用于定位页面的位置
console.log(location.host) // localhost:8080
console.log(location.hostname) // localhost
console.log(location.pathname) // /
console.log(location.port) // 8080
console.log(location.protocol) // http:
也可以检测查询参数:
// http://localhost:8080/#/home?name=jack
console.log(location.search) // ?name=jack
history
用于操作浏览器的历史记录。
console.log(history.length) // 1
history.back() // 回退
history.forward() // 前进
history.go(-1) // 回退
history.go(1) // 前进