Skip to content

JavaScript DOM对象

DOM简介

DOM(Document Object Model文档对象模型)是用来呈现以及与任意HTML或XML文档交互的API

  • DOM 是浏览器提供的一套专门用来操作网页内容的功能,DOM 的核心是把网页内容当做对象来处理,通过对象的属性和方法来操作网页内容,开发网页特效和实现用户交互

DOM 树结构如下:

  • DOM 树直观的体现了标签与标签之间的关系,每一个节点都代表一个对象
  • document对象是浏览器提供的全局对象,它是 DOM 的根节点,它提供的属性和方法都是用来访问和操作网页内容

image-20230511224731327

DOM对象

节点对象可以理解成具体的DOM对象,本文中DOM对象和节点对象意思一致

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

DOM 中包括12种类型的节点对象

  • 元素节点(Element Node)Node.ELEMENT_NODE(值为1)代表HTML中的元素,如<div><p>
  • 属性节点(Attribute Node):Node.ATTRIBUTE_NODE(值为2)代表元素的属性,现代 DOM 操作中不常作为独立节点使用
  • 文本节点(Text Node)Node.TEXT_NODE(值为3)代表元素的文本内容
  • CDATA节点(CDATA Section Node):Node.CDATA_SECTION_NODE(值为4)代表 XML 中的字符数据区域
  • 实体引用节点(Entity Reference Node):Node.ENTITY_REFERENCE_NODE(值为5)代表 XML 中的实体引用
  • 实体节点(Entity Node):Node.ENTITY_NODE(值为6)代表XML中的实体
  • 处理指令节点(Processing Instruction Node):Node.PROCESSING_INSTRUCTION_NODE(值为7)代表XML中的处理指令
  • 注释节点(Comment Node)Node.COMMENT_NODE(值为8)代表 HTML 或 XML 中的注释
  • 文档节点(Document Node)Node.DOCUMENT_NODE(值为9)代表整个文档,等同于document对象
  • 文档类型节点(DocumentType Node):Node.DOCUMENT_TYPE_NODE(值为10)代表文档类型声明,如<!DOCTYPE html>
  • 文档片段节点(DocumentFragment Node):Node.DOCUMENT_FRAGMENT_NODE(值为11)代表轻量级的 DOM 节点,可以包含多个子节点,但本身不是文档的一部分
  • 记法节点(Notation Node):Node.NOTATION_NODE(值为12)代表 XML 中的记法声明

节点原型链中,最主要的对象如下:

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

获取原型链对象代码如下:

js
function getPrototype(obj) {
  const prototypes = []
  while (obj) {
    prototypes.push(obj)
    obj = Object.getPrototypeOf(obj)
  }
  return prototypes
}

常用的 JavaScript 中的 DOM API 如下表:

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

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

  • DOM 对象不同生成的属性也不同
  • 操作属性区分大小写,多个单词使用小驼峰命名法
  • 属性值为多种类型,并不全是字符串,也可能是对。如:事件处理程序属性值为函数
  • style 属性为CSSStyleDeclaration对象

特殊的几个属性的对应关系如下:

HTML属性JS别名
classclassName
forhtmlFor

节点获取

根据各个属性来获取元素的基本选择器:

方法说明注意事项返回值
getElementById(id)根据id获取对应元素只能通过document对象调用,id大小写敏感HTMLElement
getElementsByClassName(class)根据class获取对应元素集合可以在任何元素上调用,调用该方法的元素作为本次查找的根元素HTMLCollection
getElementsByName(name)根据name获取对应元素集合只能通过document对象调用Nodelist
getElementsByTagName(tagName)根据tagName获取对应元素集合不区分大小写HTMLCollection

CSS 选择器:

方法说明注意事项返回值
querySelector('css选择器')根据css选择器获取匹配的第一个元素可以在任何元素上调用,调用该方法的元素作为本次查找的根元素HTMLElement
querySelectorAll('css选择器')根据class获取对应元素集合可以在任何元素上调用,调用该方法的元素作为本次查找的根元素Nodelist

节点属性

nodeType:以数值返回节点类型

nodeName:指定节点的名称,获取值为大写形式

tagName:用于获取标签节点的名称

nodeType说明nodeNamenodeValue
1元素节点元素名称,如DIVnull
2属性节点属性名称属性值
3文本节点#text文本内容
8注释节点#comment注释内容
9document 对象

节点集合

NodeListHTMLCollection都是包含多个节点标签的集合,大部分功能相同

  • NodelistHTMLCollection提供了item()方法来根据索引获取元素,也可以直接使用[索引]的形式
html
<div></div>
<div></div>
<script>
  //结果为NodeList
  const nodeLists = document.querySelectorAll('div')
  //结果为HTMLCollection
  const htmlCollection = document.getElementsByTagName('div')
  console.dir(nodeLists.item(0))
  console.dir(nodeLists[0])
</script>
  • HTMLCollection是动态的集合,随DOM动态变化;NodeList是静态集合,为固定快照
html
<h1>Hello World</h1>
<h1>Hello DOM</h1>
<button id="add">添加元素</button>

<script>
  const elementQuery = document.querySelectorAll('h1')
  const elementGet = document.getElementsByTagName('h1')
  document.querySelector('#add').addEventListener('click', () => {
    document.querySelector('body').insertAdjacentHTML('beforeend', '<h1>Hello JS</h1>')
    console.log('NodeList:', elementQuery, 'HTMLCollection:', elementGet)
  })
</script>
  • 节点集合对象原型中不存在map方法,但可以借用Array的原型map方法实现遍历
html
<div>Hello World</div>
<div>Hello DOM</div>

<script>
  const nodes = document.querySelectorAll('div')
  Array.prototype.map.call(nodes, (node, index) => {
    console.log(node, index)
  })
</script>
  • 伪数组也可以通过...展开运算符或者Array.from转换成数组从而实现遍历
html
<div>Hello World</div>
<div>Hello DOM</div>

<script>
  const nodes = document.querySelectorAll('div')
  Array.from(nodes).forEach(node => console.log(node))
  ;[...nodes].forEach(node => console.log(node))
</script>

NodeListHTMLCollection区别如下:

HTMLCollectionNodeList
集合元素节点的集合节点的集合,包含元素节点、属性节点、文本节点、注释节点等
静态和动态动态静态,但对节点内容修改能监听到如innerHTML
获取方式getElementsByTagName
getElementsByClassName
getElementById
querySelectorAll
是否为伪数组类数组,无法使用数组方法类数组,但本身实现了forEachkeysvaluesentries()
是否可迭代可迭代,for...of遍历可迭代,for...of遍历

节点关系

DOM 中的节点关系可大致分为:父子关系祖孙关系兄弟关系

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

  • 文本与注释也是节点,例如换行'\n'
  • HTML标签的父节点是document
节点属性说明
childNodes获取所有子节点
parentNode获取父节点
firstChild第一个子节点
lastChild最后一个子节点
nextSibling下一个兄弟节点
previousSibling上一个兄弟节点

标签关系

使用childNodes等获取的节点包括文本节点与注释节点,DOM 也提供了只操作标签的方法

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

节点内容

innerHTML:用于获取或设置一个元素内部的 HTML 内容,会解析新的 HTML 字符串,并更新 DOM 树,这通常会导致浏览器的重绘(repaint)和重排(reflow)

outerHTML:用于获取或设置一个元素的HTML内容,包括元素本身和它的所有子元素

html
<div id="myDiv">
  <p>Hello, World!</p>
</div>

<script>
  // 替换 #myDiv 内部的 <p>Hello, World!</p>
  document.getElementById('myDiv').innerHTML = '<p>Goodbye, World!</p>';
  // 直接替换了 整个 #myDiv 元素
  document.getElementById('myDiv').outerHTML = '<div><p>Goodbye, World!</p></div>';
</script>

textContent:获取或设置一个节点及其所有后代节点的纯文本内容,这包括隐藏的元素,不解析标签

innerText:表示元素及子孙节点渲染后的可视文本内容,不解析标签

html
<p>Some <strong>important</strong> text</p>
<script>
  console.log(document.querySelector('p').textContent) // Some important  text
  console.log(document.querySelector('p').innerText) // Some important text
</script>

outerText:与innerText类似,但会影响操作的标签

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

选项说明
beforebegin元素本身前面
afterend元素本身后面
afterbegin元素内部前面
beforeend元素内部后面
html
<!-- beforebegin -->
<p>
  <!-- afterbegin -->
  foo
  <!-- beforeend -->
</p>
<!-- afterend -->

节点操作

  • 创建节点:document.createElement('tagName')

  • 追加节点

方法说明
append节点最后一个子节点之后添加新节点或字符串(appendChild升级版)
prepend节点第一个子元素的之前添加新节点或字符串
before节点前面添加新节点或字符串
after节点后面添加新节点或字符串
replaceWith将节点替换为新节点或字符串
js
//插入节点
var parent = document.createElement("div");
var p = document.createElement("p");
parent.append(p);

console.log(parent.childNodes); // NodeList [ <p> ]

//插入文本
var parent = document.createElement("div");
parent.append("Some text");

console.log(parent.textContent); // "Some text"

//插入节点同时插入文本
var parent = document.createElement("div");
var p = document.createElement("p");
parent.append("Some text", p);

console.log(parent.childNodes); // NodeList [ #text "Some text", <p> ]
  • 删除节点:element.remove(),把对象从它所属的 DOM 树中删除
  • 虚拟节点:DocumentFragment,不会造成性能问题,DOM 操作频繁时使用

元素属性

常用属性

常用属性指的是元素本身的属性,使用对象.属性 = 值的方法,直接修改对象属性值

对于标准的属性可以使用 DOM 属性的方式进行操作,但对于标签的非标准的定制属性则不可以

JS 提供了方法来控制标准或非标准的属性(属性名称不区分大小写):

方法说明
getAttribute获取属性
setAttribute设置属性
removeAttribute删除属性
hasAttribute属性检测

元素也提供了attributes属性来只读的获取元素的属性

自定义属性

HTML5新增了data-自定义属性

  • 标签上以data-开头
  • 在DOM对象上以dataset对象方式获取
  • DOM对象.dataset获取自定义属性对象,属性为各个自定义属性
html
<div class='box' data-id='10'>盒子</div>
<script>
  const box = document.querySelector('.box')
  console.log(box.dataset.id)
</script>

样式属性

通过 JavaScript 设置或修改标签元素的样式属性

  • 通过style属性操作 CSS
js
//对象.style.样式属性 = 值
const box = document.querySelector('.box')
box.style.width = '200px'
box.style.marginTop = '15px'//此处的连接符使用小驼峰命名法

此处,如果width属性未在元素上定义但通过CSS定义,使用element.style.width时,获取为null,需要使用window.getComputedStyle(元素, 伪元素)方法

  • 操作类名className操作CSS
    • 如果修改的样式比较多,可以通过修改类名来操作 CSS,classNameclass别名
    • className 是使用新值换旧值,如果需要添加一个类,需要保留之前的类名
js
元素.className = 'active'
  • 通过classList追加和删除类来控制 CSS
js
// 追加一个类
元素.classList.add('类名')
// 删除一个类
元素.classList.remove('类名')
// 切换一个类,如果该类名存在则移除,不存在则添加
元素.classList.toggle('类名')
// 是否包含某个类,如果有则返回true,没有则返回false
元素.classList.contains('类名')

DOM事件

在文档、浏览器以及标签元素等在特定状态下触发的行为即为事件,如用户的点击行为、表单内容的改变行为,我们可以为不同的事件定义处理程序。JS 使用异步事件驱动的形式管理事件

事件监听

事件监听指的是让程序检测是否有事件产生,一旦有事件触发,就立即调用一个函数做出响应,也称为绑定事件或者注册事件

  • 事件监听是将事件处理函数注册到元素对象上,包括事件源事件类型事件处理函数
  • 可以给同一个 DOM 对象添加同一种类型的事件监听,它们会按照添加的顺序执行
  • 可以给动态生成的元素绑定事件
方法说明
addEventListener(type, listener)
addEventListener(type, listener, options)
addEventListener(type, listener, useCapture)
添加事件处理程序
removeEventListener(type, listener)
removeEventListener(type, listener, options)
removeEventListener(type, listener, useCapture)
移除事件处理程序,匿名函数无法移除

如给button按钮添加点击事件

html
<button class="btn1">按钮1</button>
<button class="btn2">移除按钮1事件</button>

<script>
  const btn1 = document.querySelector('.btn1')
  const btn2 = document.querySelector('.btn1')
  function alert1() {
    alert('点击事件1执行了~')
  }
  function alert2() {
    alert('点击事件2执行了~')
  }
  btn1.addEventListener('click', alert1)
  btn1.addEventListener('click', alert2)
  btn2.addEventListener('click', function () {
    btn1.removeEventListener('click', alert1)
    btn1.removeEventListener('click', alert2)
  })
</script>

事件监听还有一种写法,但由于其局限性,目前已经很少被使用,此处了解即可

  • 事件源.on事件 = function() {}

  • <button onclick="函数名()">按钮</button>

  • on方式会被覆盖(DOM L0),addEventListener方式可绑定多次,拥有事件更多特性(DOM L2)

事件类型

事件类型种类有很多,这里列举常见的事件类型

  • 鼠标事件:鼠标事件会触发在z-index层级最高的元素上
事件触发时机
click鼠标单击事件
dblclick鼠标双击事件
mouseover鼠标移动到元素上
mouseout鼠标从元素上移出
mouseenter鼠标移入时触发,不产生冒泡行为
mouseleave鼠标移出时触发,不产生冒泡行为
oncopy复制内容时触发
  • 键盘事件keydown > input > keyup
事件触发时机
keydown键盘按下触发
keyup键盘抬起触发
  • 表单事件
事件触发时机
focus获取焦点事件
blur失去焦点事件
change文本框在内容发生改变并失去焦点时触发,select/checkbox/radio 选项改变时触发事件
inputInput、textarea、select 元素的 value 被修改时触发 。而 change 是鼠标离开后或选择一个不同的 option 时触发
submit提交表单

对象.click() 方法是程序可以模拟用户点击事件,自动执行。常见的有click()、blur()、focus()

  • 页面事件
事件触发时机
load监听页面所有资源加载完毕(window对象)
DOMContentLoaded监听页面 DOM 加载完毕(document对象)无需等待样式表、图像等完全加载
scroll页面滚动时触发(window对象)
resize窗口尺寸改变的时候触发事件(window对象)
unload文档卸载时
beforeunload文档刷新或关闭时

事件对象

执行事件处理程序时,会产生当前事件相关信息的对象,即为事件对事。系统会自动做为参数传递给事件处理程序

  • 事件绑定的回调函数的第一个参数就是事件对象,一般命名为event、e。可以不显式声明事件对象,直接使用event
js
元素.addEventListener('click', function(e) {
  //其中e即为事件对象
})

元素.addEventListener('click', function() {
  //其中event即为事件对象
})

事件对象常用属性如下:

属性名类型说明
altKeyboolean事件发生时,是否按下alt按键
ctrlkeyboolean事件发生时,是否按下ctrl按键
shiftKeyboolean事件发生时,是否按下shift按键
offsetXnumber事件发生时,鼠标相对于事件源的x坐标
offsetYnumber事件发生时,鼠标相对于事件源的y坐标
targetobject事件源对象
pageXnumber事件发生时,鼠标相对于网页的x坐标
pageYnumber事件发生时,鼠标相对于网页的y坐标
clientXnumber事件发生时,鼠标相对于视口的x坐标
clientYnumber事件发生时,鼠标相对于视口的y坐标
keystring如果是键盘相关事件,则事件对象中包含该属性,表示键盘事件发生时,按下的是什么键。'Enter'回车键

事件流

事件流指的是事件完整执行过程中的流动路径

  • 例如:假设页面里有个div,当触发事件时,会经历三个阶段,分别是捕获阶段(Capturing)目标阶段(Target)冒泡阶段(Bubbling)
  • 简单来说:捕获阶段是从父到子、冒泡阶段是从子到父,并且我们只能干预捕获和冒泡阶段中的一个
  • 目标阶段指的是触发自己的事件

image-20230517171243300

捕获阶段--->目标阶段--->冒泡阶段

事件捕获

当一个元素的事件被触发时,会从 DOM 的根元素开始依次调用同名事件(从外到里)

js
DOM.addEventListener(事件类型, 事件处理函数, 是否使用捕获机制)
  • 第三个参数传入true代表是捕获阶段触发
  • 若传入false代表冒泡阶段触发,默认为false
html
<body>
  <div class="father">
        父盒子
    <div class="son">子盒子</div>
  </div>
  <script>
    // 事件流
    const father = document.querySelector('.father')
    const son = document.querySelector('.son')
    // 事件捕获
    father.addEventListener('click', function () {
      alert('我是爸爸')
    }, true)  // 事件捕获
    son.addEventListener('click', function () {
      alert('我是儿子')
    }, true) // 事件捕获
  </script>
</body>

事件冒泡

当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡(从里到外)

  • 简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的同名事件
  • 事件冒泡是默认存在的(默认第三参数为false

因为默认就有冒泡阶段的存在,所以容易导致事件影响到父级元素。若想把事件就限制在当前元素内,就需要阻止事件冒泡,阻止事件冒泡需要拿到事件对象

js
事件对象.stopPropagation()

示例:

html
<div class="father">
    父盒子
    <div class="son">子盒子</div>
</div>
<script>
  // 事件流
  const father = document.querySelector('.father')
  const son = document.querySelector('.son')
  // 点击父盒子
  father.addEventListener('click', function () {
    alert('我是爸爸')
  })
  // 点击子盒子
  son.addEventListener('click', function (e) {
    alert('我是儿子')
    e.stopPropagation() //阻止冒泡
  }) 
</script>

阻止默认行为

阻止元素发生默认行为:事件对象.preventDefault()

html
<body>
  <form action="">
    姓名: <input type="text" name="username">
    <button>提交</button>
  </form>
  <a href="http://www.baidu.com">点击跳转</a>
  <script>
    // 阻止默认行为
    const form = document.querySelector('form')
    const input = document.querySelector('[name=username]')
    form.addEventListener('submit', function (e) {
      // 如果input表单的值为空则不允许提交
      if (input.value === '') {
        // return 无法阻止提交事件
        e.preventDefault()  // 阻止提交事件
      }
    })

    document.querySelector('a').addEventListener('click', function (e) {
      e.preventDefault()
    })
  </script>
</body>

事件委托

事件委托指的是:原本需要注册在子元素的事件委托给父元素,让父元素担当事件监听的职务。是JavaScript中注册事件的常用技巧,也称为事件委派事件代理

  • 优点:减少注册次数,可以提高程序性能
  • 原理:事件委托其实是利用事件冒泡的特点,给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件(事件冒泡)

获取当前点击的元素:

js
事件对象.target.tagName //e.target事件目标

注意:

  • e.target.tagName获取的标签名要大写
  • e.target得到目标元素
最近更新