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 |
获取方式 | getElementsByTagName getElementsByClassName getElementById | 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
得到目标元素