JavaScript DOM对象
DOM简介
DOM(Document Object Model文档对象模型)是用来呈现以及与任意HTML或XML文档交互的API
- DOM 是浏览器提供的一套专门用来操作网页内容的功能,DOM 的核心是把网页内容当做对象来处理,通过对象的属性和方法来操作网页内容,开发网页特效和实现用户交互
DOM 树结构如下:
- DOM 树直观的体现了标签与标签之间的关系,每一个节点都代表一个对象
document对象是浏览器提供的全局对象,它是 DOM 的根节点,它提供的属性和方法都是用来访问和操作网页内容

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 等方法 |
获取原型链对象代码如下:
function getPrototype(obj) {
const prototypes = []
while (obj) {
prototypes.push(obj)
obj = Object.getPrototypeOf(obj)
}
return prototypes
}常用的 JavaScript 中的 DOM API 如下表:
| 方法 | 说明 |
|---|---|
document | document 是 DOM 操作的起始节点 |
document.documentElement | 文档节点即 html 标签节点 |
document.body | body 标签节点 |
| document.head | head 标签节点 |
| document.links | 超链接集合 |
| document.anchors | 所有锚点集合 |
| document.forms | form 表单集合 |
| document.images | 图片集合 |
此外,元素的标准属性具有相对应的 DOM 对象属性
- DOM 对象不同生成的属性也不同
- 操作属性区分大小写,多个单词使用小驼峰命名法
- 属性值为多种类型,并不全是字符串,也可能是对。如:事件处理程序属性值为函数
- style 属性为
CSSStyleDeclaration对象
特殊的几个属性的对应关系如下:
| HTML属性 | JS别名 |
|---|---|
| class | className |
| for | htmlFor |
节点获取
根据各个属性来获取元素的基本选择器:
| 方法 | 说明 | 注意事项 | 返回值 |
|---|---|---|---|
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 | 说明 | nodeName | nodeValue |
|---|---|---|---|
1 | 元素节点 | 元素名称,如DIV | null |
2 | 属性节点 | 属性名称 | 属性值 |
3 | 文本节点 | #text | 文本内容 |
8 | 注释节点 | #comment | 注释内容 |
9 | document 对象 |
节点集合
NodeList与HTMLCollection都是包含多个节点标签的集合,大部分功能相同
Nodelist与HTMLCollection提供了item()方法来根据索引获取元素,也可以直接使用[索引]的形式
<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是静态集合,为固定快照
<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方法实现遍历
<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转换成数组从而实现遍历
<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>NodeList与HTMLCollection区别如下:
| HTMLCollection | NodeList | |
|---|---|---|
| 集合 | 元素节点的集合 | 节点的集合,包含元素节点、属性节点、文本节点、注释节点等 |
| 静态和动态 | 动态 | 静态,但对节点内容修改能监听到如innerHTML |
| 获取方式 | getElementsByTagNamegetElementsByClassNamegetElementById | querySelectorAll |
| 是否为伪数组 | 类数组,无法使用数组方法 | 类数组,但本身实现了forEach、keys、values、entries() |
| 是否可迭代 | 可迭代,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内容,包括元素本身和它的所有子元素
<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:表示元素及子孙节点渲染后的可视文本内容,不解析标签
<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 | 元素内部后面 |
<!-- beforebegin -->
<p>
<!-- afterbegin -->
foo
<!-- beforeend -->
</p>
<!-- afterend -->节点操作
创建节点:
document.createElement('tagName')追加节点
| 方法 | 说明 |
|---|---|
append | 节点最后一个子节点之后添加新节点或字符串(appendChild升级版) |
prepend | 节点第一个子元素的之前添加新节点或字符串 |
before | 节点前面添加新节点或字符串 |
after | 节点后面添加新节点或字符串 |
replaceWith | 将节点替换为新节点或字符串 |
//插入节点
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获取自定义属性对象,属性为各个自定义属性
<div class='box' data-id='10'>盒子</div>
<script>
const box = document.querySelector('.box')
console.log(box.dataset.id)
</script>样式属性
通过 JavaScript 设置或修改标签元素的样式属性
- 通过
style属性操作 CSS
//对象.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,
className为class别名 - className 是使用新值换旧值,如果需要添加一个类,需要保留之前的类名
- 如果修改的样式比较多,可以通过修改类名来操作 CSS,
元素.className = 'active'- 通过
classList追加和删除类来控制 CSS
// 追加一个类
元素.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按钮添加点击事件
<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 选项改变时触发事件 |
input | Input、textarea、select 元素的 value 被修改时触发 。而 change 是鼠标离开后或选择一个不同的 option 时触发 |
submit | 提交表单 |
对象.click()方法是程序可以模拟用户点击事件,自动执行。常见的有click()、blur()、focus()
- 页面事件
| 事件 | 触发时机 |
|---|---|
load | 监听页面所有资源加载完毕(window对象) |
DOMContentLoaded | 监听页面 DOM 加载完毕(document对象)无需等待样式表、图像等完全加载 |
scroll | 页面滚动时触发(window对象) |
resize | 窗口尺寸改变的时候触发事件(window对象) |
unload | 文档卸载时 |
beforeunload | 文档刷新或关闭时 |
事件对象
执行事件处理程序时,会产生当前事件相关信息的对象,即为事件对事。系统会自动做为参数传递给事件处理程序
- 事件绑定的回调函数的第一个参数就是事件对象,一般命名为
event、e。可以不显式声明事件对象,直接使用event
元素.addEventListener('click', function(e) {
//其中e即为事件对象
})
元素.addEventListener('click', function() {
//其中event即为事件对象
})事件对象常用属性如下:
| 属性名 | 类型 | 说明 |
|---|---|---|
| altKey | boolean | 事件发生时,是否按下alt按键 |
| ctrlkey | boolean | 事件发生时,是否按下ctrl按键 |
| shiftKey | boolean | 事件发生时,是否按下shift按键 |
| offsetX | number | 事件发生时,鼠标相对于事件源的x坐标 |
| offsetY | number | 事件发生时,鼠标相对于事件源的y坐标 |
| target | object | 事件源对象 |
| pageX | number | 事件发生时,鼠标相对于网页的x坐标 |
| pageY | number | 事件发生时,鼠标相对于网页的y坐标 |
| clientX | number | 事件发生时,鼠标相对于视口的x坐标 |
| clientY | number | 事件发生时,鼠标相对于视口的y坐标 |
| key | string | 如果是键盘相关事件,则事件对象中包含该属性,表示键盘事件发生时,按下的是什么键。'Enter'回车键 |
事件流
事件流指的是事件完整执行过程中的流动路径
- 例如:假设页面里有个div,当触发事件时,会经历三个阶段,分别是
捕获阶段(Capturing)、目标阶段(Target)、冒泡阶段(Bubbling) - 简单来说:捕获阶段是从父到子、冒泡阶段是从子到父,并且我们只能干预捕获和冒泡阶段中的一个
- 目标阶段指的是触发自己的事件

捕获阶段--->目标阶段--->冒泡阶段
事件捕获
当一个元素的事件被触发时,会从 DOM 的根元素开始依次调用同名事件(从外到里)
DOM.addEventListener(事件类型, 事件处理函数, 是否使用捕获机制)- 第三个参数传入
true代表是捕获阶段触发 - 若传入
false代表冒泡阶段触发,默认为false
<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)
因为默认就有冒泡阶段的存在,所以容易导致事件影响到父级元素。若想把事件就限制在当前元素内,就需要阻止事件冒泡,阻止事件冒泡需要拿到事件对象
事件对象.stopPropagation()示例:
<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()
<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中注册事件的常用技巧,也称为事件委派、事件代理
- 优点:减少注册次数,可以提高程序性能
- 原理:事件委托其实是利用事件冒泡的特点,给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件(事件冒泡)
获取当前点击的元素:
事件对象.target.tagName //e.target事件目标注意:
e.target.tagName获取的标签名要大写e.target得到目标元素