Skip to content

DOM

DOM 即 Document Object Model 文档对象模型,浏览器在加载页面是会生成 DOM 对象,以供我们使用 JS 控制页面元素。

文档渲染

浏览器会将 HTML 文本内容进行渲染,并生成相应的 JS 对象,同时会对不符规则的标签进行处理。

  • 浏览器会将标签规范后渲染页面
  • 目的一让页面可以正确呈现
  • 目的二可以生成统一的 JS 可操作对象

操作时机

需要保证浏览器已经渲染了内容才可以读取的节点对象,下例将无法读取到节点对象

javascript
<script>
  const node = document.getElementById('love')
  console.log(node) //null
</script>
<h1 id="love">love</h1>

不过我们可以将脚本通过事件放在页面渲染完执行

javascript
<script>
  window.onload = () => {
    const node = document.getElementById('love')
    console.log(node)
  }
</script>
<h1 id="love">love</h1>

或使用定时器将脚本设置为异步执行

javascript
<script>
  setTimeout(() => {
    const node = document.getElementById('love')
    console.log(node)
  })
</script>
<h1 id="love">love</h1>

或将脚本设置在外部文件并使用 defer 属性加载,defer 即会等到 DOM 解析后迟延执行

javascript
<script defer="defer" src="love.js"></script>
<div id="love"></div>

节点对象

JS 中操作 DOM 的内容称为节点对象(node),即然是对象就包括操作 NODE 的属性和方法

  • 包括 12 种类型的节点对象
  • 常用了节点为 document、标签元素节点、文本节点、注释节点
  • 节点均继承自 Node 类型,所以拥有相同的属性或方法
  • document 是 DOM 操作的起始节点
节点nodeTypenodeNamenodeValue示例
元素节点(Element Nodes)1大写标签名不包含值或返回null‌ <div>、<p>、<a>
属性节点(Attribute Nodes)2属性的名称属性的值‌id href class
文本节点(Text Nodes)3#text内容
CDATA区段节点(CDATASection Nodes)4仅存在XML文档中
注释节点(Comment Nodes)8#comment注释的内容‌
文档节点(Document Nodes)9#documentnull
文档类型节点10doctype的名称null<!DOCTYPE html>

原型链

原型说明
Object根对象,提供 hasOwnProperty 等基本对象操作支持
EventTarget提供 addEventListener、removeEventListener 等事件支持方法
Node提供 firstChild、parentNode 等节点操作方法
Element提供 getElementsByTagName、querySelector 等方法
HTMLElement所有元素的基础类,提供 childNodes、nodeType、nodeName、className、nodeName 等方法
HTMLHeadingElementHead 标题元素类

打印原型链

js
function prototype(el) {
    const prototypes = []
    prototypes.push(el.__proto__)
    prototypes.push(...(el.__proto__ ? prototype(el.__proto__) : []))
    return prototypes
}

对象特征

利用Object.assign添加或者修改属性。

常用节点

JS 提供了访问常用节点的 api

方法说明
documentdocument 是 DOM 操作的起始节点
document.documentElement文档节点即 html 标签节点
document.bodybody 标签节点
document.headhead 标签节点
document.links超链接集合
document.anchors所有锚点集合
document.formsform 表单集合
document.images图片集合

DOCUMENT

document 是 window 对象的属性,是由 HTMLDocument 类实现的实例。

节点属性

使用 data 属性可以获取文本与注释内容

javascript
<div id="app">
  love.com
  <!-- love 注释内容-->
</div>

<script>
  const app = document.querySelector('#app')
  console.log(app.childNodes[0].data)
  console.log(app.childNodes[1].data)
</script>

节点集合

Nodelist 与 HTMLCollection 都是包含多个节点标签的集合,大部分功能也是相同的。

  • getElementsByClassName 等方法返回的是 NodeList
  • querySelectorAll 返回的是 HTMLCollection
  • NodeList 节点列表是动态的,即内容添加后会动态更新

item

Nodelist 与 HTMLCollection 提供了 item()方法来根据索引获取元素,也可以使用数组索引。

javascript
<script>
  const nodes = document.getElementsByTagName('div')
  console.dir(nodes.item(0))
</script>

<script>
  const nodes = document.getElementsByTagName('div')
  console.dir(nodes[0])
</script>

namedItem

HTMLCollection 具有 namedItem 方法可以按 name 或 id 属性来获取元素

html
<div name="app">
  <div id="love">houdunren.com</div>
  <div name="you">houdunwang.com</div>
</div>

<script>
  const nodes = document.getElementsByTagName('div')
  console.dir(nodes.namedItem('love'))
   console.dir(nodes.namedItem('you'))
</script>

数字索引时使用 item 方法,字符串索引时使用 namedItem 或 items 方法

动态与静态

通过 getElementsByTagname 等 getElementsBy... 函数获取的 Nodelist 与 HTMLCollection 集合是动态的,即有元素添加或移动操作将实时反映最新状态。

  • 使用 getElement...返回的都是动态的集合
  • 使用 querySelectorAll 返回的是静态集合

动态特性

下例中通过按钮动态添加元素后,获取的元素集合是动态的,而不是上次获取的固定快照。

html
<h1>baidu.com</h1>
<h1>jd.com</h1>
<button id="add">添加元素</button>

<script>
  let elements = document.getElementsByTagName('h1')
  console.log(elements)
  let button = document.querySelector('#add')
  button.addEventListener('click', () => {
    document.querySelector('body').insertAdjacentHTML('beforeend', '<h1>love you</h1>')
    console.log(elements)
  })
</script>

document.querySelectorAll 获取的集合是静态的

遍历节点

forOf

Nodelist 与 HTMLCollection 是类数组的可迭代对象所以可以使用 for...of 进行遍历

forEach

Nodelist 节点列表也可以使用 forEach 来进行遍历,但 HTMLCollection 则不可以

call/apply

节点集合对象原型中不存在 map 方法,但可以借用 Array 的原型 map 方法实现遍历

javascript
Array.prototype.map.call(nodes, (node, index) => {
    console.log(node, index)
})

Array.from

Array.from 用于将类数组转为组件,并提供第二个迭代函数。所以可以借用 Array.from 实现遍历

javascript
 Array.from(nodes, (node, index) => {
    console.log(node, index)
})

节点关系

下面是通过节点关系获取相应元素的方法

节点属性说明
childNodes获取所有子节点
parentNode获取父节点
firstChild第一个子节点
lastChild最后一个子节点
nextSibling下一个兄弟节点
previousSibling上一个兄弟节点

标签关系

使用 childNodes 等获取的节点包括文本与注释,但这不是我们常用的,为此系统也提供了只操作元素的关系方法。

基础知识

下面是处理标签关系的常用 API

节点属性说明
parentElement获取父元素
children获取所有子元素
childElementCount子标签元素的数量
firstElementChild第一个子标签
lastElementChild最后一个子标签
previousElementSibling上一个兄弟标签
nextElementSibling下一个兄弟标签
contains返回布尔值,判断传入的节点是否为该节点的后代节点

标签获取

getElementById

使用 ID 选择是非常方便的选择具有 ID 值的节点元素,但注意 ID 应该是唯一的,getElementById 只能通过 document 访问。

getElementsByName

使用 getElementByName 获取设置了 name 属性的元素。

  • 返回 NodeList 节点列表对象
  • NodeList 顺序为元素在文档中的顺序
  • 需要在 document 对象上使用

getElementsByTagName

使用 getElementsByTagName 用于按标签名获取元素

  • 返回 HTMLCollection 节点列表对象
  • 是不区分大小的获取

getElementsByClassName

getElementsByClassName 用于按 class 样式属性值获取元素集合

  • 设置多个值时顺序无关,指包含这些 class 属性的元素

样式选择器

querySelectorAll

使用 querySelectorAll 根据 CSS 选择器获取 Nodelist 节点列表

  • 获取的 NodeList 节点列表是静态的,添加或删除元素后不变

querySelector

querySelector 使用 CSS 选择器获取一个元素。

matches

用于检测元素是否是指定的样式选择器匹配

closest

查找最近的符合选择器的祖先元素(包括自身)

标准属性

属性别名

有些属性名与 JS 关键词冲突,系统已经起了别名

属性别名
classclassName
forhtmlFor

元素特征

对于标准的属性可以使用 DOM 属性的方式进行操作,但对于标签的非标准的定制属性则不可以。但 JS 提供了方法来控制标准或非标准的属性

可以理解为元素的属性分两个地方保存,DOM 属性中记录标准属性,特征中记录标准和定制属性

  • 使用特征操作时属性名称不区分大小写
  • 特征值都为字符串类型
方法说明
getAttribute获取属性
setAttribute设置属性
removeAttribute删除属性
hasAttribute属性检测

特征是可迭代对象,下面使用 for...of 来进行遍历操作

attributes

元素提供了 attributes 属性可以只读的获取元素的属性

html
<div class="love" data-content="静思">love.com</div>
<script>
  let houdunwang = document.querySelector('.love')
  console.dir(houdunwang.attributes['data-content'].nodeValue) //静思
	console.dir(houdunwang.attributes['data-content'].value) //静思
</script>

自定义特征

虽然可以随意定义特征并使用 getAttribute 等方法管理,但很容易造成与标签的现在或未来属性重名。建议使用以 data-为前缀的自定义特征处理,针对这种定义方式 JS 也提供了接口方便操作。

  • 元素中以 data-为前缀的属性会添加到属性集中
  • 使用元素的 dataset 可获取属性集中的属性

属性同步

下面对 input 值使用属性设置,但并没有同步到特征

html
<input type="text" name="package" value="love.com" />
<script>
  const package = document.querySelector(`[name='package']`)
  package.value = 'love.com'
  console.log(package.getAttribute('value'))//love.com
</script>

但改变 input 的特征 value 会同步到 DOM 对象属性

html
<input type="text" name="package" value="love.com" />
<script>
  const package = document.querySelector(`[name='package']`)
  package.setAttribute('value', 'love.com')
  console.log(package.value) //love.com
</script>

创建节点

创建节点的就是构建出 DOM 对象,然后根据需要添加到其他节点中

createElement

使用 createElement 方法可以标签节点对象>

使用 PROMISE 结合节点操作来加载外部 JAVASCRIPT 文件

javascript
function js(file) {
  return new Promise((resolve, reject) => {
    let js = document.createElement('script')
    js.type = 'text/javascript'
    js.src = file
    js.onload = resolve
    js.onerror = reject
    document.head.appendChild(js)
  })
}

js('outside.js')
  .then(() => console.log('加载成功'))
  .catch((error) => console.log(`${error.target.src} 加载失败`))

cloneNode&importNode

使用 cloneNode 和 document.importNode 用于复制节点对象操作

  • cloneNode 是节点方法
  • cloneNode 参数为 true 时递归复制子节点即深拷贝
  • importNode 是 documet 对象方法

复制 div#app 节点并添加到 body 元素中

html
<div id="app">love</div>
<script>
  let app = document.querySelector('#app')
  let newApp = app.cloneNode(true)
  document.body.appendChild(newApp)
</script>

document.importNode 方法是部分 IE 浏览器不支持的,也是复制节点对象的方法

  • 第一个参数为节点对象
  • 第二个参数为 true 时递归复制
html
<div id="app">love</div>
<script>
  let app = document.querySelector('#app')
  let newApp = document.importNode(app, true)
  document.body.appendChild(newApp)
</script>

节点内容

innerHTML

inneHTML 用于向标签中添加 html 内容,同时触发浏览器的解析器重绘 DOM。

  • innerHTML 中只解析 HTML 标签语法,所以其中的 script 不会做为 JS 处理

outerHTML

outerHTML 与 innerHTML 的区别是包含父标签

textContent 与 innerText

textContent 与 innerText 是访问或添加文本内容到元素中

  • textContentb 部分 IE 浏览器版本不支持
  • innerText 部分 FireFox 浏览器版本不支持
  • 获取时忽略所有标签,只获取文本内容
  • 设置时将内容中的标签当文本对待不进行标签解析

设置时将标签当文本对待,即转为 HTML 实体内容

html
<div id="app">
  <div class="love" data="hd">love.com</div>
</div>
<script>
  let app = document.querySelector('#app')
  app.textContent="<h1>love</h1>" //<h1>love</h1>
</script>

outerText

与 innerText 差别是会影响所操作的标签

insertAdjacentText

将文本插入到元素指定位置,不会对文本中的标签进行解析,包括以下位置

选项说明
beforebegin元素本身前面
afterend元素本身后面
afterbegin元素内部前面
beforeend元素内部后面

节点管理

推荐方法

方法说明
append节点尾部添加新节点或字符串
prepend节点开始添加新节点或字符串
before节点前面添加新节点或字符串
after节点后面添加新节点或字符串
replaceWith将节点替换为新节点或字符串

将 h2 移动到 h1 之前

html
<h1>first.com@h1</h1>
<h2>second@h2</h2>
<script>
  let h1 = document.querySelector('h1')
  let h2 = document.querySelector('h2')
  h1.before(h2)
</script>

insertAdjacentHTML

将 html 文本插入到元素指定位置,浏览器会对文本进行标签解析,包括以下位置

选项说明
beforebegin元素本身前面
afterend元素本身后面
afterbegin元素内部前面
beforeend元素内部后面

insertAdjacentElement

insertAdjacentElement() 方法将指定元素插入到元素的指定位置,包括以下位置

  • 第一个参数是位置
  • 第二个参数为新元素节点
选项说明
beforebegin元素本身前面
afterend元素本身后面
afterbegin元素内部前面
beforeend元素内部后面

两者区别就是插入类型不同,insertAdjacentElement插入的元素需要继承type 'Element'.

样式管理

通过 DOM 修改样式可以通过更改元素的 class 属性或通过 style 对象设置行样式来完成。

  • 建议使用 class 控制样式,将任务交给 CSS 处理,更简单高效

classList

如果对类单独进行控制使用 classList 属性操作

方法说明
node.classList.add添加类名
node.classList.remove删除类名
node.classList.toggle切换类名
node.classList.contains类名检测

在元素的原有 class 上添加新 class

html
<div id="app" class="d-flex container">love</div>
<script>
  let app = document.getElementById('app')
  app.classList.add('love') //love为类名作为参数
</script>

使用 classList 也可以移除 class 列表中的部分 class

html
<div id="app" class="d-flex container">love</div>
<script>
  let app = document.getElementById('app')
  app.classList.remove('container')
</script>

使用 toggle 切换类,即类已经存在时删除,不存在时添加

html
<div id="app" class="d-flex container">love</div>
<script>
  let app = document.getElementById('app')
  app.addEventListener('click', function () {
    this.classList.toggle('love')
  })
</script>

使用 contains 检查 class 是否存在

html
<div id="app" class="d-flex container">love</div>
<script>
  let app = document.getElementById('app')
  console.log(app.classList.contains('container')) //true
  console.log(app.classList.contains('love')) //false
</script>

设置行样式

使用 style 对象可以对样式属性单独设置,使用 cssText 可以批量设置行样式

样式属性设置

使用节点的 style 对象来设置行样式

js
app.style.backgroundColor = 'red'

批量设置行样式

使用 cssText 属性可以批量设置行样式,属性名和写 CSS 一样不需要考虑驼峰命名

js
app.style.cssText = `background-color:red;color:yellow`
app.setAttribute('style', `background-color:red;color:yellow;`)

获取样式

可以通过 style 对象,window.getComputedStyle 对象获取样式属性,下面进行说明

style

可以使用 DOM 对象的 style 属性读取行样式

  • style 对象不能获取行样式外定义的样式
js
console.log(app.style.backgroundColor)
console.log(app.style.margin)
console.log(app.style.marginTop)
console.log(app.style.color)

getComputedStyle

使用 window.getComputedStyle 可获取所有应用在元素上的样式属性

  • 函数第一个参数为元素
  • 第二个参数为伪类
  • 这是计算后的样式属性,所以取得的单位和定义时的可能会有不同
html
<style>
    div {
        font-size: 35px;
        color: yellow;
    }
</style>
<div id="app" style="background-color: red; margin: 20px;">love</div>
<script>
    let app = document.getElementById('app')
    let style = window.getComputedStyle(app)
    console.log(style.fontSize.slice(0, -2))
    console.log(parseInt(style.fontSize))
    app.style.fontSize = parseInt(style.fontSize) + 21 + 'px'
    console.log(parseInt(style.fontSize))
</script>