DOM 和 BOM | 青训营笔记

90 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天

DOM API

  • DOM: Document Object Model

DOM API 是实现网页操作的 API (W3C 标准),包括 DOM 树的操作、事件、样式、位置等。VUEREACT 封装了 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 节点的属性,比如 idclassNameinnerHTML 等。用于获取或设置节点的属性。

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'

nodeNamenodeTypenodeValue 等属性也是 property。如:

const box = document.getElementById('box')
box.nodeName // 'DIV'
box.nodeType // 1,1 表示元素节点
box.nodeValue // null

attribute

attribute 是 DOM 节点的属性,比如 idclassstyle 等。用于获取或设置节点的属性。

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;')

总的来说,propertyattribute 的区别是: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   // 遍历下一个兄弟节点
    }
  }
}
  • propertyattribute 的区别?

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) // 前进