DOM(Document Object Model)文档对象模型
将整个HTML文档每个标签当做一个对象,这个对象下包含了许多属性和方法,通过操作这些属性或调用方法实现对HTML的动态更新
console.log(document); // 打印整个HTML文档对象
console.log(typeof document); // object
本篇介绍DOM中的方法,以及如何对页面中的元素进行操作
js中的元素就是html标签,文本、标签、属性等都是节点
获取页面元素
想要操作页面中的标签、属性等,首先需要先获取标签
document.querySelector()
返回满足条件的第一个文档元素,未找到则返回null
可以使用css中的选择器
// <div></div>
let box = document.querySelector('div')
// <div class="box"></div>
let box = document.querySelector('.box')
// <div> <p></p> </div>
let p = document.querySelector('div p')
document.querySelectorAll()
返回满足条件的所有元素,是一个伪数组(有length,但是不能调用数组方法)
可以使用css中的选择器
// <p class="p1"></p>
// <p class="p2"></p>
let p = document.querySelectorAll('p')
console.log(p[0]) // 打印 <p class="p1"></p>
document.getElementById() | 根据id返回第一个匹配到的标签 |
document.getElementsByTagName() | 根据标签名,返回伪数组,包含所有匹配到的元素 |
document.getElementsByClassName() | 根据class类名,同样返回伪数组 |
document.getElementsByName() | 根据name属性值,同样返回伪数组 |
document.documentElement | 返回html根标签 |
document.body | 返回body标签 |
元素的属性
获取到的页面元素会自带一些属性,元素.属性就可以得到属性值
let box = querySelector('div')
console.log(box.clientWidth) // 打印box元素的宽度
clientWidth | 元素宽度(width + padding) |
clientHeight | 元素高度(height + padding) |
offsetWidth | 元素布局宽度(width + padding + border) |
offsetHeight | 元素布局高度(height + padding + border) |
scrollWidth | 实际内容宽度(width + padding,包括隐藏的内容,比如overflow隐藏的宽度) |
scrollHeight | 实际内容高度(height + padding,包括隐藏的内容,比如overflow隐藏的宽度) |
offsetLeft | 与左侧参照物的距离 (参照物为最近的带有定位的祖先元素,没有则为body) |
offsetTop | 与上方参照物的距离 (参照物为最近的带有定位的祖先元素,没有则为body) |
scrollTop | 被卷去的高度(例如body在向下滚动时,它的scrollTop值会从0越来越大) |
节点操作
查找元素节点
.parentNode | 返回父节点 |
.children | 返回所有子节点 |
.previousElementSibling | 返回上一个兄弟节点 |
.nextElementSibling | 返回下一个兄弟节点 |
.childNodes | 返回所有子节点(包括文本节点,比如文字、空格、换行等等) |
.previousSibling | 返回上一个兄弟节点(包括文本节点) |
.nextSibling | 返回下一个兄弟节点(包括文本节点) |
// <ul><li><p>haha</p></li></ul>
const li = document.querySelector('li')
console.log(li.parentNode) // 打印ul标签
console.log(li.children) // 打印p标签
console.log(li.nextElementSibling) // 打印null
增加元素节点
document.createElement() | 在js中创建一个标签 |
.appendChild() | 将节点添加到目标节点的子元素的末尾 |
.prepend() | 将节点添加到目标节点的子元素的开头 |
.append() | 添加至末尾,可以一次添加多个,并且可以添加文本节点 |
const ul = document.querySelector('ul') // 获取ul标签
const li = document.createElement('li') // 创建一个li标签
ul.appendChild(li) // ul标签内,末尾增加一个li标签
const ul = document.querySelector('ul')
const li = document.createElement('li')
ul.append(li,'aaa') // ul标签内,末尾增加一个li标签,li标签后增加aaa文字
删除元素节点
.remove() | 将目标节点删除 |
.removeChild() | 将目标节点的指定子元素删除 |
const ul = document.querySelector('ul') // 获取ul标签
const li = document.querySelectorAll('li') // 获取所有li标签
li[0].remove() // 删除第一个li标签
ul.removeChild(li[1]) // 删除第二个li标签
元素操作__内容
.innerText | 获取 / 修改元素文本内容(不包含标签) |
.innerHTML | 获取 / 修改元素内容(包含标签、文本) |
// <p><span>haha</span></p>
const p = document.querySelector('p') // 获取p标签
// 获取内容
console.log(p.innerText) // 打印 haha
// 修改内容
p.innerText = 'aaa'; // span标签将被覆盖,p标签内只有文字aaa
innerText 和 innerHTML 的区别在于,innerHTML能够解析标签,在获取时会打印标签,修改时能够解析标签,而innerText会将标签以文本形式呈现在页面中
元素操作__原生属性
直接使用 标签.属性 进行操作(仅限于原生属性,并且此方法不可用于class类名)
// <img src="./xx.png" alt="高清图片" />
const img = document.querySelector('img')
// 获取属性值
console.log(img.alt) // 打印'高清图片'
// 修改属性值
img.src = './aaa.png'; // 修改图片路径
元素操作__样式
使用 标签.style.样式 进行操作(样式名带有 - 连字符,需要去除 -,并将 - 后第一个字母大写)
这种方法是js直接添加行内样式,所以权重仅次于 !important
const box = document.querySelector('div')
box.style.width = '200px'; // 将宽度设置为200px
box.style.backgroundColor = '#FFF'; // 将背景色设置为白色
元素操作__类名
.className | 覆盖类名 |
.classList.add() | 添加类名 |
.classList.remove() | 删除类名 |
.classList.toggle() | 切换类名 (有就删除,没有就添加) |
.classList.contains() | 判断是否含有某个类名 (有就返回true,没有就返回false) |
// <div class="box"></div>
const box = document.querySelector('div')
box.className = 'btn'; // 原本的所有类名被top覆盖(不推荐使用)
box.className += ' btn'; // 添加btn类名(有空格作为间隔)(即使类名重复也会添加)
box.classList.add('aa') // 添加aa类名
box.classList.remove('bb') // 删除bb类名
box.classList.toggle('cc') // 原本有cc类名就删除,没有就添加
box.classList.contains('box') // 打印true
元素操作__自定义属性
.hasAttribute() | 查询是否有该属性,返回 true / false |
.getAttribute() | 获取指定属性的属性值 |
.setAttribute() | 设置指定属性的属性值 |
// <div aa="bb"></div>
const box = document.querySelector('div')
console.log(box.hisAttribute('aa')) // 打印true,判断box上是否有aa属性
console.log(box.getAttribute('aa')) // 打印bb,获取aa属性的属性值
box.setAttribute('aa','cc') // 修改aa属性的属性值为cc
//(如果没有aa属性则会添加一个aa属性)
还可以在标签中使用 data-xx="" 的方法自定义属性,js中使用 标签.dataset.xx 获取和修改
// <div data-id="haha"></div>
const box = document.querySelector('div')
// 获取属性值
console.log(box.dataset.id) // 打印 haha
// 修改属性值
box.dataset.id = 'aa'; // 设置该自定义属性的值为aa
元素操作__其他属性
.disabled | 按钮 / input框 的禁用状态,设为 true / '任意字符串' 为禁用,false / '' 为不禁用 |
.checked | 复选框的选中状态,设为 true / '任意字符串' 为选中,false / '' 为不选中 |
// <input text="checkbox" />
const input = document.querySelector('input')
input.disabled = 'true'; // 禁用该复选框
input.checked = 'true'; // 默认选中该复选框
DOM事件
对元素进行事件绑定, 由不同行为触发函数,可以获取到元素对象以及事件对象等信息
事件绑定
绑定事件就是进行点击等行为时会触发函数
绑定事件有三种方式:DOM 0级(行内绑定、js获取元素绑定),DOM 2级(事件监听)
DOM 0级事件
重复绑定事件时会覆盖,事件名前有on
// 行内绑定事件
<div onclick="fun()">点我</div>
function fun(){
alert('haha')
}
// onclick点击事件,每当点击一次目标元素时,便执行一次函数
// js获取元素绑定事件
<div>点我<div>
const box = document.querySelector('div')
box.onclick = function(){
console.log('haha')
}
DOM 2级事件
重复绑定事件时不会覆盖,事件名前没有on
// 事件监听
<div>点我</div>
const box = document.querySelector('div')
box.addEventListener('click',function(){
console.log('haha')
})
// addEventListener() 添加事件监听
// 参数1:事件类型(事件名)
// 参数2:执行函数
// 参数3:可选,判断在什么阶段触发,后面会详细介绍
事件解绑
DOM 0级事件
btn.onclick = null; // 解绑(重新赋值)
DOM 2级事件
function fun(){};
btn.addEventListener('click',fun) // 绑定事件
btn.removeEventListener('click',fun) // 解绑事件
// removeEventListener 移除事件监听
// 参数1:想要解绑的事件类型(事件名)
// 参数2:想要解绑的函数名
// 想要移除事件监听,在绑定时需要传入函数名,而不是直接定义
// 因为解绑时需要填写对应的函数名
事件类型
鼠标事件
click | 鼠标左键点击元素 |
dblclick | 鼠标左键双击元素 |
mouseenter | 鼠标指针移入元素 |
mouseleave | 鼠标指针移出元素 |
mousemove | 鼠标指针在元素内移动时持续触发 |
键盘事件
keydown | 按下任意按键 |
keypress | 除Shift,Fn,CapsLock外任意键被按住(连续触发) |
keyup | 释放任意按键 |
表单事件
input | 输入框内容改变时触发 |
change | 输入框内容改变并失去焦点时触发 |
select | 文本被选中时触发(input标签、textarea标签) |
reset | 点击重置按钮时触发 |
submit | 点击提交按钮时触发 |
焦点事件
focus | 元素获得焦点时触发 |
blur | 元素失去焦点时触发 |
其他
copy | 元素内容被拷贝时触发 |
load | 一个资源及其相关资源已完成加载时触发 |
事件流
事件流的定义:事件完整执行过程中的流动路径
事件流的三个阶段:捕获阶段->目标阶段->冒泡阶段
一个点击事件,不仅点击到的是目标元素,也是他的父元素,所以就有了事件流,事件流就是一个事件的传播过程
比如现在给一个p标签绑定一个点击事件,点击p标签时
会从window对象开始,一级一级地向子元素传播,这个阶段就是捕获阶段,传播到p标签时,就是目标阶段,然后继续向父元素传播,就是冒泡阶段
addEventListener事件监听的第三个参数,可以声明该事件在什么阶段触发
false(默认):在冒泡阶段触发;true:在捕获阶段触发
举个例子:
给box绑定了一个点击事件,给body也绑定了一个点击事件
div.addEventListener('click',function(){
console.log('div')
})
document.body.addEventListener('click',function(){
console.log('body')
})
点击box时,body的点击事件也会触发,结果如下:
可以看到,先打印出box,再打印body,此时addEventListener的第三个参数为默认值,即false,表示在冒泡阶段触发,冒泡阶段在目标阶段之后,所以后打印body
此时我们将body的点击事件加上第三个参数,值为true
document.body.addEventListener('click',function(){
console.log('body')
},true)
再次点击box,结果如下:
此时先打印body,再打印box,因为true表示在捕获阶段触发,捕获阶段在目标阶段之前,所以先打印body
事件对象
当事件发生后,浏览器会把当前事件相关的信息会封装成一个对象
获取事件对象:事件函数的第一个形参
box.addEventListener('click',function(e){
console.log(e); // 事件对象
console.log(e.target); // 触发事件的元素
})
事件对象中的属性
target | 返回触发事件的节点 |
currentTarget | 返回事件绑定的节点 |
eventPhase | 返回一个整数,表示事件流在传播阶段的位置 (1:捕获阶段,2:目标阶段,3:冒泡阶段) |
type | 返回一个字符串,表示事件类型,区分大小写 (点击事件打印:click) |
timeStamp | 返回一个毫秒时间戳,表示事件发生的时间 (在页面加载完毕后从0开始计时) |
clientX / clientY | 获取鼠标事件触发时的鼠标坐标 (距离客户端边缘的距离) |
pageX / pageY | 获取鼠标事件触发时的鼠标坐标 (距离页面边缘的距离) |
screenX / screenY | 获取鼠标事件触发时的鼠标坐标 (距离屏幕边缘的距离) |
key / keyCode | 获取键盘事件输入的字符和按键编号 (按键a的key:a,keyCode:65) |
关于target和currentTarget,在上面的box例子中,点击box时,body绑定的事件中的target是box元素,而currentTarget是body元素,box是触发事件的源头,而body是事件绑定的节点
阻止事件冒泡
不允许在冒泡阶段触发目标元素的祖先元素的绑定事件
box.addEventListener('click',function(e){
e.stopProagation()
}
document.body.addEventListener('click',function(){
console.log('body')
}
此时点击box元素,不会再触发body的点击事件
阻止默认行为
一些标签都有默认的行为,比如a标签点击会跳转,我们可以阻止这个跳转发生
// 第一种
e.preventDefault();
// 第二种
return false;
当然第二种会return,所以一般放在代码的最后
事件委托
又叫事件委派、事件代理
比如在ul列表中,有多个li标签,现在想给每一个li标签都绑定一个点击事件,就需要获取到所有的li标签,然后循环绑定
事件委托:获取到ul标签,给ul绑定点击事件,通过事件对象的target属性获取到触发事件的节点,也就是每一个li标签
const ul = document.querySelector('ul');
ul.addEventListener('click',function(e){
if(e.target.tagName === 'LI'){
// 代码块
}
}
上面的示例中使用e.target.tagName,这个属性返回节点名称的大写字符串,通过判断可以精确每次点击的是否为li标签,也可以使用e.target.classList.contains()来判断类名等等
事件委托对动态生成的子元素也生效,如果直接给li标签绑定事件,后面js添加进去新的li标签又需要重新绑定,而事件委托则不需要。