JavaScript-DOM

83 阅读26分钟

参考文献

  1. 后盾人编程doc.houdunren.com/
  2. JavaScript权威指南

基础知识

操作文档 HTML 的 JS 处理方式为 DOM 即 Document Object Model 文档对象模型。如果对 HTML 很了解使用 DOM 并不复杂。

浏览器在加载页面是会生成 DOM 对象,以供我们使用 JS 控制页面元素。

文档渲染

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

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

标签修复

在 html 中只有内容houdunren.com 而没有任何标签时,通过浏览器的 检查>元素 标签查看会自动修复成以下格式的内容

下面 H1 标签结束错误并且属性也没有引号,浏览器在渲染中会进行修复

<body>
  <h1 id=houdunren>后盾人<h1>
</body>

处理后的结果

<html>
  <head></head>
  <body>
    <h1 id="houdunren">后盾人</h1>
  </body>
</html>

表格处理

表格 tabel 中不允许有内容,浏览器在渲染过程中会进行处理

<table>
  houdunren.com
  <tr>
    <td>houdunwang.com</td>
  </tr>
</table>

渲染后会添加 tbody 标签并将 table 中的字符移出

houdunren.com
<table>
  <tbody>
      <tr>
      <td>houdunwang.com</td>
    </tr>
  </tbody>
</table>

标签移动

所有内容要写在 BODY 标签中,下面的 SCRIPT 标签写在了 BODY 后面,浏览器渲染后也会进行处理

<body></body>
<script>
  console.dir('houdunren.com')
</script>

渲染后处理的结果

<body>
  <script>
    console.dir('houdunren.com')
  </script>
</body>

操作时机

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

<script>
  const node = document.getElementById('houdunwang')
  console.log(node) //null
</script>
<h1 id="houdunwang">houdunren.com</h1>

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

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

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

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

也可以放在文档加载后的事件处理函数中

<script>
  window.onload = function () {
    let hd = document.getElementById('hd')
    console.log(hd)
  }
</script>
<div id="hd">houdunren</div>

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

<script defer="defer" src="3.js"></script>
<div id="houdunwang"></div>

节点对象

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

  • 包括 12 种类型的节点对象
  • 常用的节点为 document、标签元素节点、文本节点、注释节点
  • 节点均继承自 Node 类型,所以拥有相同的属性或方法
  • document 是 DOM 操作的起始节点
<body id="houdunwang">
  <!-- 后盾人 -->
</body>
<script>
    // document节点 noteType为9
  console.log(document.nodeType)

  // 第一个子节点为<!DOCTYPE html>,且nodetype为10
  console.log(document.childNodes.item(0).nodeType)

  // body 是标签节点 nodeType为1
  console.log(document.body.nodeType)

  // body的属性节点 nodeType 为2
  console.log(document.body.attributes[0].nodeType)

    // body的第一个节点为文本节点,nodeType为3
  console.log(document.body.childNodes.item(0).nodeType)

  // body的第二个节点为注释,nodeType类型为8
  console.log(document.body.childNodes[1].nodeType)
</script>

原型链

在浏览器渲染过程中会将文档内容生成为不同的对象,我伙来对下例中的 h1 标签进行讨论,其他节点情况相似

  • 不同类型节点由专有的构造函数创建对象
  • 使用 console.dir 可以打印出 DOM 节点对象结构
  • 节点也是对象所以也具有 JS 对象的特征
<h1 id="houdunwang">houdunren.com</h1>
<script>
  function prototype(el) {
    console.dir(el.__proto__)
    el.__proto__ ? prototype(el.__proto__) : ''
  }
  const node = document.getElementById('houdunwang')
  prototype(node)
</script>

最终得到的节点的原型链为

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

我们将上面的方法优化一下,实现提取节点原型链的数组

<h2 id="h2 value">houdunren.com</h2>
<input type="text" id="inputId" value="后盾人" />
<script>
    function prototype(el) {
        const prototypes = []
        prototypes.push(el.__proto__)
        prototypes.push(...(el.__proto__ ? prototype(el.__proto__) : []))
        return prototypes
    }
    const h2 = document.querySelector('h2')
    const input = document.querySelector('input')

    console.log(prototype(input))
</script>

下面为标题元素增加两个原型方法,改变颜色与隐藏元素

<h2 onclick="this.color('red')">houdunren.com</h2>
<script>
  const h2 = document.querySelector('h2')
  HTMLHeadingElement.prototype = Object.assign(HTMLHeadingElement.prototype, {
    color(color) {
      this.style.color = color
    },
    hide() {
      this.style.display = 'none'
    },
  })
</script>

对象特征

即然 DOM 与我们其他 JS 创建的对象特征相仿,所以也可以为 DOM 对象添加属性或方法。

对于系统应用的属性,应该明确含义不应该随意使用,比如 ID 是用于标识元素唯一属性,不能用于其他目地

  • 后面会讲到其他解决方案,来自定义属性,ID 属性可以直接修改但是不建议这么做
let hd = document.getElementById('hd')
hd.id = 'houdunren.com'
console.log(hd)
  • title 用于显示提示文档也不应该用于其他目地
<div id="hd">houdunren.com</div>
<script>
  let hd = document.getElementById('hd')
  hd.title = 'houdunren.com'
  console.log(hd)
</script>

下面是为对象合并属性的示例

<div id="hd">houdunren.com</div>
<script>
  let hd = document.getElementById('hd')

  Object.assign(hd, {
    //设置标签内容
    innerHTML: '向军大叔',
    color: 'red',
    change() {
      this.innerHTML = '后盾人'
      this.style.color = this.color
    },
    onclick() {
      this.change()
    },
  })
</script>

使用对象特性更改样式属性

<div id="hd">houdunren.com</div>
<script>
  let hd = document.getElementById('hd')
  Object.assign(hd.style, {
    color: 'white',
    backgroundColor: 'red',
  })
</script>

选择Document元素

每个Window对象都有一个document属性,引用一个Document对象,这个Document对象是操作文档的核心对象

客户端的js程序经常需要操作文档中一个或多个元素。全局的document属性引用Document对象,而Document对象有head和body属性分别引用和标签对应的Element对象。但一个程序想要操作文档中嵌入层级更多的元素,必须先通过某种方式获取或选择表示该元素的Element对象

通过CSS选择元素

在讲解css选择元素前需要补充一些选择符的知识,就是css选择符就是通过元素类型(标签)、ID、类名、属性以及元素在文档中的位置来引用元素的一种标记,通过这种标记能很好的定位元素

方法名参数功能描述
querySelector包含一个或多个要匹配的选择器的 DOM 字符串DOMString。该字符串必须是有效的 CSS 选择器字符串;如果不是,则引发SYNTAX_ERR异常。querySelector方法接受一个css选择符字符串作为参数返回它在文档中找到的第一个匹配的元素。如果没有找到返回null
querySelectorAll一个包含一个或多个匹配的选择器的字符串。其必须是一个有效的 CSS 选择器字符串,如果不是,会抛出 SyntaxError 异常。querySelectorAll方法接受一个css选择符字符串作为参数并返回所有与该选择符匹配的元素,返回的元素是一个类似数组的NodeList对象,可以对该对象进行迭代
closest一个包含一个或多个匹配的选择器的字符串。若调用它的元素不是选择符对应的元素,该方法会向上进行查找匹配元素。也就是与querySelector查找方向相反
matches一个包含一个或多个匹配的选择器的字符串。该方法不会返回祖先,也不会返回后代,只会检查是否与选择符匹配,如果匹配返回true;否则,返回false

🔔

注意querySelector不接受伪元素选择器

querySelectorAll

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

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

获取所有 div 元素

<div class="xiangjun">向军大叔</div>
<div id="app">
  <div class="houdunren houdunwang">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
</div>

<script>
  const app = document.getElementById('app')
  const nodes = app.querySelectorAll('div')
  console.log(nodes.length) //2
</script>

获取 id 为 app 元素的,class 为 houdunren 的后代元素

<div class="xiangjun">向军大叔</div>
<div id="app">
  <div class="houdunren houdunwang">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
</div>
<script>
  const nodes = document.querySelectorAll('#app .houdunren')
  console.log(nodes.length) //2
</script>

根据元素属性值获取元素集合

<div id="app">
  <div class="houdunren houdunwang" data="hd">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
</div>
<script>
  const nodes = document.querySelectorAll(`#app .houdunren[data='hd']`)
  console.log(nodes.length) //2
</script>

再来看一些通过样式选择器查找元素

<div id="app">
  <div class="houdunren">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
  <span>后盾人</span>
</div>

<script>
  //查找紧临兄弟元素
  console.log(document.querySelectorAll('.houdunren+div.houdunwang'))

  //查找最后一个 div 子元素
  console.log(document.querySelector('#app div:last-of-type'))

  //查找第二个 div 元素
  console.log(document.querySelector('#app div:nth-of-type(2)').innerHTML)
</script>

querySelector

querySelector 使用 CSS 选择器获取一个元素,下面是根据属性获取单个元素

<div id="app">
  <div class="houdunren houdunwang" data="hd">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
</div>
<script>
  const node = app.querySelector(`#app .houdunren[data='hd']`)
  console.log(node)
</script>

matches

用于检测元素是否是指定的样式选择器匹配,下面过滤掉所有 name 属性的 LI 元素

<div id="app">
  <li>houdunren</li>
  <li>向军大叔</li>
  <li name="houdunwang">houdunwang.com</li>
</div>
<script>
  const nodes = [...document.querySelectorAll('li')].filter(node => {
    return !node.matches(`[name]`)
  })
  console.log(nodes)
</script>

closest

查找最近的符合选择器的祖先元素(包括自身),下例查找父级拥有 .comment类的元素

<div class="comment">
  <ul class="comment">
    <li>houdunren.com</li>
  </ul>
</div>

<script>
  const li = document.getElementsByTagName('li')[0]
  const node = li.closest(`.comment`)
  //结果为 ul.comment
  console.log(node)
</script>

其它选择属性的方法

除了querySelector和querySelectorAll方法以外,DOM也定义了一些老式的元素选择方法。

方法参数功能描述
document.getElementById(id)id属性的值,不用加#通过元素 id 来查找元素
document.getElementsByTagName(name)标签名通过标签名来查找元素
document.getElementsByClassName(name)元素类名通过类名来查找元素

系统提供了丰富的选择节点(NODE)的操作方法,下面我们来一一说明。

getElementById

使用 ID 选择是非常方便的选择具有 ID 值的节点元素,但注意 ID 应该是唯一的

只能通过 document 对象上使用

<div id="houdunren">houdunren.com</div>
<script>
  const node = document.getElementById('houdunren')
  console.dir(node)
</script>

getElementById 只能通过 document 访问,不能通过元素读取拥有 ID 的子元素,下面的操作将产生错误

<div id="app">
  houdunren.com
  <div id="houdunwang">houdunwang.com</div>
</div>
<script>
  const app = document.getElementById('app')
  const node = app.getElementById('houdunwang') //app.getElementById is not a function
  console.log(node)
</script>

下面自定义函数来支持批量按 ID 选择元素

<div id="houdunren">houdunren.com</div>
<div id="app"></div>
<script>
  function getByElementIds(ids) {
    return ids.map((id) => document.getElementById(id))
  }
  let nodes = getByElementIds(['houdunren', 'app'])
  console.dir(nodes)
</script>

拥有 ID 的元素可做为 WINDOW 的属性进行访问

<div id="app">
  houdunren.com
</div>
<script>
  console.log(app.innerHTML)
</script>

如果声明了变量这种访问方式将无效,所以并不建议使用这种方式访问对象

<div id="app">
  houdunren.com
</div>
<script>
  let app = 'houdunwang'
  console.log(app.innerHTML)
</script>

getElementsByName

使用 getElementByName 获取设置了 name 属性的元素,虽然在 DIV 等元素上同样有效,但一般用来对表单元素进行操作时使用。

  • 返回 NodeList 节点列表对象
  • NodeList 顺序为元素在文档中的顺序
  • 需要在 document 对象上使用
<div name="houdunren">houdunren.com</div>
<input type="text" name="username" />

<script>
  const div = document.getElementsByName('houdunren')
  console.dir(div)
  const input = document.getElementsByName('username')
  console.dir(input)
</script>

getElementsByTagName

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

  • 返回 HTMLCollection 节点列表对象
  • 是不区分大小的获取
<div name="houdunren">houdunren.com</div>
<div id="app"></div>
<script>
  const divs = document.getElementsByTagName('div')
  console.dir(divs)
</script>

通配符

可以使用通配符 ***** 获取所有元素

<div name="houdunren">houdunren.com</div>
<div id="app"></div>

<script>
  const nodes = document.getElementsByTagName('*')
  console.dir(nodes)
</script>

某个元素也可以使用通配置符 ***** 获取后代元素,下面获取 id 为 houdunren 的所有后代元素

<div id="houdunren">
  <span>houdunren.com</span>
  <span>houdunwang.com</span>
</div>

<script>
  const nodes = document.getElementsByTagName('*').namedItem('houdunren').getElementsByTagName('*')
  console.dir(nodes)
</script>

getElementsByClassName

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

  • 设置多个值时顺序无关,指包含这些 class 属性的元素
<div class="houdunren houdunwang xiangjun">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>

<script>
  const nodes = document.getElementsByClassName('houdunwang')
  console.log(nodes.length) //2

  //查找同时具有 houdunwang 与 houdunren 两个class属性的元素
  const tags = document.body.getElementsByClassName('houdunwang houdunren ')
  console.log(tags.length) //1
</script>

下面我们来自己开发一个与 getElementsByClassName 相同的功能函数

<div class="houdunren houdunwang xiangjun">houdunren.com</div>
<div class="houdunwang">houdunwang.com</div>
<script>
  function getByClassName(names) {
    //将用户参数转为数组,并过滤掉空值
    const classNames = names.split(/\s+/).filter(t => t)

    return Array.from(document.getElementsByTagName('*')).filter(tag => {
      // 提取标签的所有 class 值为数组
      return classNames.every(className => {
        const names = tag.className
          .toUpperCase()
          .split(/\s+/)
          .filter(t => t)

        //检索标签是否存在class
        return names.some(name => name == className.toUpperCase())
      })
    })
  }

  console.log(getByClassName('houdunwang houdunren '))
</script>

预选择元素

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

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

当然js还支持直接通过id和名字来索引元素

<img id="title1"></img>
<img id="title2"></img>
<a name="name1"></a>
<a name="name2"></a>
<script>
  console.log(console.log(document.images.title1))
  console.log(console.log(document.anchors.name1))
  console.log(console.log(document.anchors.name2))
</script>

节点集合

两种节点集合

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

  • getElementsByClassName 等方法返回的是 NodeList
  • querySelectorAll 返回的是 HTMLCollection
  • NodeList 节点列表是动态的,即内容添加后会动态更新
<div></div>
<div></div>
<script>
  //结果为NodeList
  console.log(document.querySelectorAll('div'))

  //结果为HTMLCollection
  console.log(document.getElementsByTagName('div'))
</script>

length

Nodelist 与 HTMLCollection 包含 length 属性,记录了节点元素的数量

<div name="app">
  <div id="houdunren">houdunren.com</div>
  <div name="houdunwang">houdunwang.com</div>
</div>
<script>
  const nodes = document.getElementsByTagName('div')
  for (let i = 0; i < nodes.length; i++) {
    console.log(nodes[i])
  }
</script>

item

Nodelist 与 HTMLCollection 提供了 item()方法来根据索引获取元素

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

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

使用数组索引获取更方便

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

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

namedItem

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

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

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

也可以使用数组或属性方式获取

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

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

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

<h1 id="hd">houdunren.com</h1>
<h1 name="xj">向军大叔</h1>
<script>
  let items = document.getElementsByTagName('h1')
  console.log(items[0])
  console.log(items['xj'])
</script>

动态与静态

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

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

比如下面的案例

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

<h1>houdunren.com</h1>
<h1>houdunwang.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>向军大叔</h1>')
    console.log(elements.length)  // 随元素添加而增大
  })
</script>

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

<h1>houdunren.com</h1>
<h1>houdunwang.com</h1>
<button id="add">添加元素</button>

<script>
  let elements = document.querySelectorAll('h1')
  console.log(elements.length)
  let button = document.querySelector('#add')
  button.addEventListener('click', () => {
    document.querySelector('body').insertAdjacentHTML('beforeend', '<h1>向军大叔</h1>')
    console.log(elements.length) // 2
  })
</script>

如果需要保存静态集合,则需要对集合进行复制

<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
<script>
  const nodes = document.getElementsByTagName('div')
  const clone = Array.prototype.slice.call(nodes)
  console.log(nodes.length);//2
  document.body.appendChild(document.createElement('div'))
  console.log(nodes.length);//3
  console.log(clone.length);//2
</script>

遍历节点集合

forOf

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

<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
<script>
  const nodes = document.getElementsByTagName('div')
  for (const item of nodes) {
    console.log(item)
  }
</script>

forEach

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

<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>
<script>
  const nodes = document.querySelectorAll('div')
  nodes.forEach((node, key) => {
    console.log(node)
  })
</script>

call/apply

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

<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>

<script>
  const nodes = document.querySelectorAll('div')
  Array.prototype.map.call(nodes, (node, index) => {
    console.log(node, index)
  })
</script>

当然也可以使用以下方式操作

;[].filter.call(nodes, node => {
    console.log(node)
})

Array.from

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

<div id="houdunren">houdunren.com</div>
<div name="houdunwang">houdunwang.com</div>

<script>
  const nodes = document.getElementsByTagName('div')
  Array.from(nodes, (node, index) => {
    console.log(node, index)
  })
</script>

展开语法

下面使用点语法转换节点为数组

<h1>houdunren.com</h1>
<h1>houdunwang.com</h1>
<script>
  let elements = document.getElementsByTagName('h1')
  console.log(elements)
  ;[...elements].map((item) => {
    item.addEventListener('click', function () {
      this.style.textTransform = 'uppercase'
    })
  })
</script>

文档结构与遍历

不包含文本节点和注释结点的API

下面的api默认忽视文本节点和注释结点

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

包含文本节点和注释结点的API

下面的api不会忽视文本节点和注释结点

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

属性

节点属性

不同类型的节点拥有不同属性,下面是节点属性的说明与示例

nodeType

nodeType 指以数值返回节点类型

nodeType说明
1元素节点
2属性节点
3文本节点
8注释节点
9document 对象
<div id="app">
  <div class="houdunren" data="hd">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
  <div class="xiangjun"><!-- 向军大叔 --></div>
</div>
<script>
  const node = document.querySelector(`#app`)
  console.log(node.nodeType) //1
  console.log(node.firstChild.nodeType) //3
  console.log(node.attributes.id.nodeType) //2

  const xj = document.querySelector('.xiangjun')
  console.log(xj.childNodes[0].nodeType) //8
</script>

下面是根据指定的 nodeType 递归获取节点元素的示例

可获取文本、注释、标签等节点元素

<!-- 后盾人 -->
后盾人 houdunren.com
<div id="app">
  <ul>
    <li>
      <span></span>
      <span>
        <!-- 向军 -->
      </span>
    </li>
    <li><span></span><span></span></li>
    <li><span></span><span></span></li>
  </ul>
</div>

<script>
  function all(el, nodeType = 1) {
    const nodes = []

    Array.from(el.childNodes).map(node => {
      if (node.nodeType == nodeType) nodes.push(node)

      if (node.nodeType == 1) nodes.push(...all(node, nodeType))
    })
    return nodes
  }
  console.log(all(document.body))
</script>

Prototype

当然也可以使用对象的原型进行检测

  • section 、main、aslide 标签的原型对象为 HTMLElement
  • 其他非系统标签的原型对象为 HTMLUnknownElement
let h1 = document.querySelector('h1')
let p = document.querySelector('p')
console.log(h1 instanceof HTMLHeadingElement) //true
console.log(p instanceof HTMLHeadingElement) //false
console.log(p instanceof Element) //true

下例是通过构建函数获取节点的示例

<!-- 后盾人 -->
后盾人 houdunren.com
<div id="app">
  <ul>
    <li>
      <span></span>
      <span>
        <!-- 向军 -->
      </span>
    </li>
    <li><span></span><span></span></li>
    <li><span></span><span></span></li>
  </ul>
</div>

<script>
  function all(el, prototype) {
    const nodes = []

    Array.from(el.childNodes).map(node => {
      if (node instanceof prototype) nodes.push(node)

      if (node.nodeType == 1) nodes.push(...all(node, prototype))
    })
    return nodes
  }

  console.log(all(document.body, HTMLSpanElement))
</script>

nodeName

nodeName 指定节点的名称

获取值为大写形式

nodeTypenodeName
1元素名称如 DIV
2属性名称
3#text
8#comment

下面来操作 nodeName

<div id="app">
  <div class="houdunren" data="hd">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
  <div class="xiangjun"><!-- 向军大叔 --></div>
  <span> 后盾人</span>
</div>
<script>
  const div = document.querySelector(`#app`)
  const span = document.querySelector('span')

  // 标签节点为大写的标签名DIV
  console.log(div.nodeName)
  console.log(span.nodeName)

  // 文本节点为 #text
  console.log(div.firstChild.nodeName)

  //属性节点为属性名
  console.log(div.attributes.id.nodeName)

  // 注释节点为#comment
  const xj = document.querySelector('.xiangjun')
  console.log(xj.childNodes[0].nodeName)
</script>

tagName

nodeName 可以获取不限于元素的节点名,tagName 仅能用于获取标签节点的名称

  • tagName 存在于 Element 类的原型中
  • 文本、注释节点值为 undefined
  • 获取的值为大写的标签名
<div id="app">
  <div class="houdunren" data="hd">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
  <div class="xiangjun"><!-- 向军大叔 --></div>
  <span> 后盾人</span>
</div>
<script>
  const div = document.querySelector(`#app`)
  const span = document.querySelector('span')

  // 标签节点为大写的标签名 如DIV、SPAN
  console.log(div.tagName)
  console.log(span.tagName)

  // 文本节点为undefined
  console.log(div.firstChild.tagName)

  //属性节点为undefined
  console.log(div.attributes.id.tagName)

  // 注释节点为 undefined
  const xj = document.querySelector('.xiangjun')
  console.log(xj.childNodes[0].tagName)
</script>

nodeValue

使用 nodeValue 或 data 函数获取节点值,也可以使用节点的 data 属性获取节点内容

nodeTypenodeValue
1null
2属性值
3文本内容
8注释内容

下面来看 nodeValue 的示例

<div id="app">
  <div class="houdunren">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
  <div class="xiangjun"><!-- 向军大叔 --></div>
</div>
<script>
  const node = document.querySelector(`#app`)
  //标签的 nodeValue 值为 null
  console.log(node.nodeValue)

  //属性的 nodeVale 值为属性值
  console.log(node.attributes.id.nodeValue)

  //文本的 nodeValue 值为文本内容
  const houdunwang = document.querySelector('.houdunwang')
  console.log(houdunwang.firstChild.nodeValue)

  //注释的 nodeValue 值为注释内容
  const xj = document.querySelector('.xiangjun')
  console.log(xj.childNodes[0].nodeValue)
</script>

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

<div id="app">
  houdunren.com
  <!-- 后盾人 注释内容-->
</div>

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

树状节点

下面获取标签树状结构即多级标签结构,来加深一下节点的使用

<div id="app">
  <ul>
    <li><span></span><span></span></li>
    <li><span></span><span></span></li>
    <li><span></span><span></span></li>
  </ul>
</div>

<script>
function tree(el) {
  return Array.from(el.childNodes)
    .filter(node =>node.tagName)
    .map(node => ({
      name: node.nodeName,
      children: tree(node),
    }))
}
console.log(tree(document.getElementById('app')))

上例结果如下

Array(2)
0: {name: 'HEAD', children: Array(4)}
1: {name: 'BODY', children: Array(2)}

标准属性

元素的标准属性具有相对应的 DOM 对象属性

  • 操作属性区分大小写
  • 多个单词属性命名规则为第一个单词小写,其他单词大写
  • 属性值是多类型并不全是字符串,也可能是对象等
  • 事件处理程序属性值为函数
  • style 属性为 CSSStyleDeclaration 对象
  • DOM 对象不同生成的属性也不同

属性别名

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

属性别名
classclassName
forhtmlFor

操作属性

元素的标准属性可以直接进行操作,下面是直接设置元素的 className

<div id="app">
  <div class="houdunren" data="hd">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
</div>
<script>
  const app = document.querySelector(`#app`)
  app.className = 'houdunren houdunwang'
</script>

下面设置图像元素的标准属性

<img src="" alt="" />
<script>
  let img = document.images[0]
  img.src = 'https://www.houdurnen.com/avatar.jpg'
  img.alt = '后盾人'
</script>

使用 hidden 隐藏元素

<div id="app">houdunren.com</div>
<script>
  const app = document.querySelector('#app')
  app.addEventListener('click', function () {
    this.hidden = true
  })
</script>

多类型

大部分属性值是都是字符串,但并不是全部,下例中需要转换为数值后进行数据运算

<input type="number" name="age" value="88" />

<script>
  let input = document.getElementsByName('age').item(0)
  input.value = parseInt(input.value) + 100
</script>

下面表单 checked 属性值为 Boolean 类型

<label for="hot"> <input id="hot" type="checkbox" name="hot" />热门 </label>
<script>
  const node = document.querySelector(`[name='hot']`)
  node.addEventListener('change', function () {
    console.log(this.checked)
  })
</script>

属性值并都与 HTML 定义的值一样,下面返回的 href 属性值是完整链接

<a href="#houdunren" id="home">后盾人</a>
<script>
  const node = document.querySelector(`#home`)
  console.log(node.href)
</script>

操作属性

Element类定义了下面四种方法用于增删改查结点特征,同时HTML元素的属性(标准属性)也在表示这些元素的HTMLElement对象上具有相应的属性。

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

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

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

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

<div id="app" content="后盾人" color="red">houdunwang.com</div>
<script>
  const app = document.querySelector('#app')
  for (const { name, value } of app.attributes) {
    console.log(name, value)
  }
</script>

属性值都为字符串,所以数值类型需要进行转换

<input type="number" name="age" value="88" />
<script>
  let input = document.getElementsByName('age').item(0)
  let value = input.getAttribute('value') * 1 + 100
  input.setAttribute('value', value)
</script>

使用 removeAttribute 删除元素的 class 属性,并通过 hasAttribute 进行检测删除结果

<div class="houdunwang">houdunwang.com</div>
<script>
  let houdunwang = document.querySelector('.houdunwang')
  houdunwang.removeAttribute('class')
  console.log(houdunwang.hasAttribute('class')) //false
</script>

特征值与 HTML 定义是一致的,这和属性值是不同的

<a href="#houdunren" id="home">后盾人</a>
<script>
  const node = document.querySelector(`#home`)

  // http://127.0.0.1:5500/test.html#houdunren
  console.log(node.href)

  // #houdunren
  console.log(node.getAttribute('href'))
</script>

attributes

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

<div class="houdunwang" data-content="后盾人">houdunwang.com</div>
<script>
  let houdunwang = document.querySelector('.houdunwang')
  console.dir(houdunwang.attributes['class'].nodeValue) //houdunwang
  console.dir(houdunwang.attributes['data-content'].nodeValue) //后盾人
</script>

自定义特征

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

  • 元素中以 data-为前缀的属性会添加到属性集中
  • 使用元素的 dataset 可获取属性集中的属性
  • 改变 dataset 的值也会影响到元素上

下面演示使用属性集设置 DIV 标签内容

<div class="houdunwang" data-content="后盾人" data-color="red">houdunwang.com</div>

<script>
  let houdunwang = document.querySelector('.houdunwang')
  let content = houdunwang.dataset.content
  console.log(content) //后盾人
  houdunwang.innerHTML = `<span style="color:${houdunwang.dataset.color}">${content}</span>`
</script>

多个单词的特征使用驼峰命名方式读取

<div class="houdunwang" data-title-color="red">houdunwang.com</div>
<script>
  let houdunwang = document.querySelector('.houdunwang')
  houdunwang.innerHTML = `
    <span style="color:${houdunwang.dataset.titleColor}">${houdunwang.innerHTML}</span>
  `
</script>

改变 dataset 值也会影响到页面元素上

<div class="houdunwang" data-title-color="red">houdunwang.com</div>
<script>
  let houdunwang = document.querySelector('.houdunwang')
  houdunwang.addEventListener('click', function () {
    this.dataset.titleColor = ['red', 'green', 'blue'][Math.floor(Math.random() * 3)]
    this.style.color = this.dataset.titleColor
  })
</script>

属性同步

特征和属性是记录元素属性的两个不同场所,大部分更改会进行同步操作。

下面使用属性更改了 className,会自动同步到了特征集中,反之亦然

<div id="app" class="red">houdunren.com</div>
<script>
  const app = document.querySelector('#app')
  app.className = 'houdunwang'
  console.log(app.getAttribute('class')) //houdunwang
  app.setAttribute('class', 'blue')
  console.log(app.className) //blue
</script>

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

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

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

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

元素内容

作为HTML的内容

innerHTML

inneHTML 用于向标签中添加 html 内容,设置InnerHTML通常效率很高。不过要注意,通过+=操作符给innerHTML追加文本的效率不高。因为这个操作既会涉及序列化操作,也会涉及解析操作:先把元素内容转换为字符串,然后再把新字符串转换回元素内容,也就是浏览器的解析器会重绘DOM

使用 innertHTML 操作会重绘元素,下面在点击第二次就没有效果了

  • 因为对#app 内容进行了重绘,即删除原内容然后设置新内容
  • 重绘后产生的 button 对象没有事件
  • 重绘后又产生了新 img 对象,所以在控制台中可看到新图片在加载
<div id="app">
  <button>houdunren.com</button>
  <img src="1.jpg" alt="" />
</div>
<script>
  const app = document.querySelector('#app')
  app.querySelector('button').addEventListener('click', function () {
    alert(this.innerHTML)
    this.parentElement.innerHTML += '<hr/>向军大叔'
  })
</script>

outerHTML

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

  • outerHTML 不会删除原来的旧元素
  • 只是用新内容替换替换旧内容,旧内容(元素)依然存在

下面将 div#app 替换为新内容

<div id="app">
  <div class="houdunren" data="hd">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
</div>
<script>
  let app = document.querySelector('#app')
  console.log(app.outerHTML)

  app.outerHTML = '<h1>后盾人</h1>'
</script>

使用 innerHTML 内容是被删除然后使用新内容

<div id="app">
  houdunren.com
</div>
<script>
  const app = document.querySelector('#app')
  console.log(app)
  app.innerHTML = 'houdunwang.com'
  console.log(app)
</script>

而使用 outerHTML 是保留旧内容,页面中使用新内容

<div id="app">
  houdunren.com
</div>
<script>
  const app = document.querySelector('#app')
  console.log(app)
  app.outerHTML = 'houdunwang.com'
  console.log(app)
</script>

insertAdjacentHTHL

另一个相关的Element方法是insertAdjacentHTHL,用于插入与指定元素“相邻”(adjacent)的任意HTML标记字符串。要插入的标签作为第二个参数传入,而“相邻”的精确含义取决于第一个参数的值。第一个参数可以是以下字符串值中的一个:“beforebegin" "afterbegin" "beforeend" "afterend"。

作为纯文本的内容

textConten和innerText

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

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

获取时忽略内容中的所有标签

<div id="app">
  <h1>houdunren.com</h1>
</div>
<script>
  let app = document.querySelector('#app')
  console.log(app.textContent)
</script>

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

<div id="app">
  <div class="houdunren" data="hd">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
</div>
<script>
  let app = document.querySelector('#app')
  app.textContent="<h1>后盾人</h1>"
</script>

insertAdjacentText

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

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

添加文本内容到 div#app 前面

<div id="app">
  <div class="houdunren" data="hd">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
</div>
<script>
  let app = document.querySelector('#app')
  let span = document.createElement('span')
  app.insertAdjacentText('beforebegin', '<h1>后盾人</h1>')
</script>

<script>元素中的文本

行内(即那些没有src 属性的)<script>元素有一个text属性,可以用于获取它们的文本。浏览器永远不会显示<script>元素的内容,HTML解析器会忽略脚本中的尖括号和&字符。这就让<script>元素成为在Web应用中嵌入任意文本数据的理想场所。只要把这个元素的type属性设置为某个值(如text/x-custom-data),明确它不是可执行的JavaScript代码即可。这样,JavaScript解释器将会忽略这个脚本,但该元素还会出现在文档树中,它的text属性可以返回你在其中保存的数据。

创建、插入和删除节点

我们已经知道了如何获取及使用HTML和纯文本字符串修改文档内容。也知道了可以遍历Document,查找构成文档的个别Element和Text节点。当然,在个别节点的层级修改文档也是可能的。Document类定义了创建Element对象的方法,而Element和Text对象拥有在树中插入、删除和替换节点的方法

推荐方法

方法说明
append节点尾部添加新节点或字符串
prepend节点开始添加新节点或字符串
before节点前面添加新节点或字符串
after节点后面添加新节点或字符串
replaceWith将节点替换为新节点或字符串
cloneNode拷贝结点,因为如果某个元素已经在文档中了,你有把它插入到其他地方,那它会转移到新位置,而不会复制到一个新的过去。所以可以使用cloneNode来复制一个节点
removeremove方法可以将结点或文本删除

在标签内容后面添加新内容

<div id="app">
  houdunren.com
</div>
<script>
  let app = document.querySelector('#app')
  app.append('-houdunwang.com')
</script>

同时添加多个内容,包括字符串与元素标签

<div id="app">
  houdunren.com
</div>
<script>
  let app = document.querySelector('#app')
  let h1 = document.createElement('h1')
  h1.append('后盾人')
  app.append('@', h1)
</script>

将标签替换为新内容

<div id="app">
  houdunren.com
</div>
<script>
  let app = document.querySelector('#app')
  let h1 = document.createElement('h1')
  h1.append('houdunwang.com')
  app.replaceWith(h1)
</script>

将 h2 移动到 h1 之前

<h1>houdunren.com@h1</h1>
<h2>houdunwang@h2</h2>
<script>
  let h1 = document.querySelector('h1')
  let h2 = document.querySelector('h2')
  h1.before(h2)
</script>

使用 remove 方法可以删除节点

<div id="app">
  houdunren.com
</div>
<script>
  let app = document.querySelector('#app')
  app.remove()
</script>

insertAdjacentHTML

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

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

在 div#app 前添加 HTML 文本

<div id="app">
  <div class="houdunren" data="hd">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
</div>
<script>
  let app = document.querySelector('#app')
  let span = document.createElement('span')
  app.insertAdjacentHTML('beforebegin', '<h1>后盾人</h1>')
</script>

insertAdjacentElement

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

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

在 div#app 前插入 span 标签

<div id="app">
  <div class="houdunren" data="hd">houdunren.com</div>
  <div class="houdunwang">houdunwang.com</div>
</div>
<script>
  let app = document.querySelector('#app')
  let span = document.createElement('span')
  span.innerHTML = '后盾人'
  app.insertAdjacentElement('beforebegin', span)
</script>

古老方法

下面列表过去使用的操作节点的方法,现在不建议使用了。但在阅读老代码时可来此查看语法

方法说明
appendChild添加节点
insertBefore用于插入元素到另一个元素的前面
removeChild删除节点
replaceChild进行节点的替换操作

DocumentFragment

当对节点进行添加、删除等操作时,都会引起页面回流来重新渲染页面,即重新渲染颜色,尺寸,大小、位置等等。所以会带来对性能的影响。

解决以上问题可以使用以下几种方式

  1. 可以将 DOM 写成 html 字符串,然后使用 innerHTML 添加到页面中,但这种操作会比较麻烦,且不方便使用节点操作的相关方法。
  2. 使用 createDocumentFragment 来管理节点时,此时节点都在内存中,而不是 DOM 树中。对节点的操作不会引发页面回流,带来比较好的性能体验。

DocumentFragment 特点

  • createDocumentFragment 父节点为 null
  • 继承自 node 所以可以使用 NODE 的属性和方法
  • createDocumentFragment 创建的是文档碎片,节点类型 nodeType 为 11。因为不在 DOM 树中所以只能通过 JS 进行操作
  • 添加 createDocumentFragment 添加到 DOM 后,就不可以再操作 createDocumentFragment 元素了,这与 DOM 操作是不同的
  • 将文档 DOM 添加到 createDocumentFragment 时,会移除文档中的 DOM 元素
  • createDocumentFragment 创建的节点添加到其他节点上时,会将子节点一并添加
  • createDocumentFragment 是虚拟节点对象,不直接操作 DOM 所以性能更好
  • 在排序/移动等大量 DOM 操作时建议使用 createDocumentFragment

操作 CSS

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

🔔

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

批量设置

使用 JS 的 className 可以批量设置样式

<div id="app" class="d-flex container">后盾人</div>
<script>
  let app = document.getElementById('app')
  app.className = 'houdunwang'
</script>

也可以通过特征的方式来更改

<div id="app" class="d-flex container">后盾人</div>
<script>
  let app = document.getElementById('app')
  app.setAttribute('class', 'houdunwang')
</script>

classList

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

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

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

<div id="app" class="d-flex container">后盾人</div>
<script>
  let app = document.getElementById('app')
  app.classList.add('houdunwang')
</script>

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

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

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

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

使用 contains 检查 class 是否存在

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

设置行样式

行样式对象

每个节点都有style属性,这个style属性是一个对象(CSSStyleDeclaration)。是对元素上的style属性的抽象。但是其属性都是驼峰命名法(因为-连字符会被JavaScript视为减号)

样式属性设置

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

多个单词的属性使用驼峰进行命名

<div id="app" class="d-flex container">后盾人</div>
<script>
  let app = document.getElementById('app')
  app.style.backgroundColor = 'red'
  app.style.color = 'yellow'
</script>

批量设置行样式

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

<div id="app" class="d-flex container">后盾人</div>
<script>
  let app = document.getElementById('app')
  app.style.cssText = `background-color:red;color:yellow`
</script>

也可以通过 setAttribute 改变 style 特征来批量设置样式

<div id="app" class="d-flex container">后盾人</div>
<script>
  let app = document.getElementById('app')
  app.setAttribute('style', `background-color:red;color:yellow;`)
</script>

计算样式

通过style属性获取的CSSStyleDeclaration获取的样式仅仅包含元素标签中的style中的样式,而不能获取样式表中的样式。所以这时候就需要用到计算样式了。通过getComputedStyle方法可以获得包含style中样式和样式表中的样式在内的所有样式,并且这个方法调用后也返回的也是CSSStyleDeclaration对象。只不过这个对象是只读的,并且还有一个缺陷就是它不能返回简写属性,比如margin。

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

  • 函数第一个参数为元素
  • 第二个参数为伪类

这是计算后的样式属性,所以取得的单位和定义时的可能会有不同

<style>
  div {
    font-size: 35px;
    color: yellow;
  }
</style>
<div id="app" style="background-color: red; margin: 20px;">后盾人</div>
<script>
  let app = document.getElementById('app')
  let fontSize = window.getComputedStyle(app).fontSize
  console.log(fontSize.slice(0, -2))
  console.log(parseInt(fontSize))
</script>

操作样式表

除了上面介绍的通过class和style来操作样式以外,还可以通过操作style标签和link标签来操作样式,比如下面实现了对主题的切换

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style id="theme">
    body{
      width:100vw;
      height: 100vh;
      background-color: black;
    }
  </style>
</head>
<body>
  <button onclick="changeTheme()">切换主题</button>
   <script>
     let st = document.querySelector("#theme")
     console.log("a");
     function changeTheme(){
      console.log("a");
       st.disabled = !st.disabled
     }
   </script>
</body>
</html>

CSS动画与事件

使用JavaScript还可以通过为元素添加移除class来完成过渡或事件。并且动画和过渡都有对应的事件派发,可以通过它们来获取事件对象进而完成更多的操作

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .transparent{
      opacity: 0;
    }
    .fadeable{
      transition: opacity .5s ease-in;
    }
    #box{
      width:100px;
      height: 300px;
      background-color: aquamarine;
    }
  </style>
</head>
<body>
  <div id="box" class="fadeable"></div>
  <button onclick="change()">显隐</button>
  <script>
    let box = document.querySelector("#box")
    function change(){
      box.classList.toggle('transparent')
    }
    box.addEventListener('transitionstart',(TransitionEvent)=>{
      console.log(TransitionEvent);
      console.log("动画开始了");
    })
    box.addEventListener('transitionend',()=>{
      console.log("动画结束了");
    })
  </script>
</body>
</html>

对于animation也与transition类似,具体可见MDN