文章目录
- 1. DOM介绍
- 1.1 什么是DOM
- 1.2 概念
- 1.3 关系
- 1.4 HelloWorld
- 1.5 document对象
- 2. DOM节点
- 2.1 元素节点
- 2.1.1 获取已有的元素节点
- 2.1.1 `document.getElementById()`
- 2.1.2 `document.getElementsByClassName()`
- 2.1.3 `document.getElementsByTagName()`
- 2.1.4 `document.getElementsByTagName()`
- 2.1.5 `document.getElementsByName()`
- 2.1.6 `document.querySelectorAll()`
- 2.1.7 `document.querySelector()`
- 2.1.8 `document.createElement()`
- 2.1.2 通过某个元素获取其他节点
- 2.2 文本节点
- 2.3 属性节点(Attr)
- 2.4 事件
- 2.4.1 直接在元素属性中设置
- 2.4.2 设置回调函数
- 2.4.3 元素.addActionListener()
- 2.5 文档的加载
- 2.6 练习
- 2.6.1 切换图片
- 2.6.2 实现按钮之间相互配合
- 3. DOM的修改
- 3.1 添加节点
- 3.2 删除节点
- 3.3 练习
- 3.4 复制节点
- 3.5 修改CSS样式
- 3.6 读取CSS样式
- 3.6.1 getComputedStyle()
- 3.6.2 通过属性读取样式
- 3.7 修改class
- 4. 事件
- 4.1 事件对象简介
- 4.2 Event对象
- 4.3 冒泡
- 4.4 事件的委派
- 4.5 事件的捕获
- 5. 其他
- 5.1 定时器
- 5.1.1 单次定时器
- 5.1.2 多次定时器
- 5.1.3 原理
- 5.2 事件循环
- 5.3 消息队列
1. DOM介绍
DOM – 李立超 | lilichao.com
前面在学习JS的时候发现,似乎JS和网页并没有太大的关系。换句话说,我们所编写的JS代码,除了是写在网页中以外,并没有和网页产生任何实质的联系。
而DOM就是一种使用JS来操作网页的技术
1.1 什么是DOM
DOM,全称Document Object Model,中文翻译为文档对象模型。
DOM属于Web API的一部分。Web API中定义了非常多的对象,通过这些对象可以完成对网页的各种操作(添加删除元素、发送请求、操作浏览器等)
- DOM中的D意为Document,即文档。所谓文档就是指整个网页,换言之,DOM是用来操作网页的。
- O意为Object,即对象。DOM将网页中的每一部分内容都转换为了对象。
- M意为Model,即模型。模型用来表示对象之间的关系,也就是父子元素、祖先后代、兄弟元素等,明确关系后我们便可以通过任意一个对象去获取其他的对象。
<!DOCTYPE html>
<html lang="zh">
<head>
<title>My Title</title>
</head>
<body>
<h1>A Heading</h1>
<a href="#">Link Text</a>
</body>
</html>
对于上面的代码,我们可以得到如下DOM树
1.2 概念
在DOM标准下,网页中的每一个部分都会转换为对象。这些对象有一个共同的称呼——节点(Node)。
一个页面将会由多个节点构成,虽然都称为节点,但是它们却有着不同的类型:
- 文档节点
- 元素节点
- 文本节点
- 属性节点
- …
每一个节点都有其不同的作用,文档节点表示整个网页,元素节点表示某个标签,文本节点表示网页中的文本内容,属性节点表示标签中的各种属性。如果从对象的结构上来讲,这些对象都有一个共同的父类Node。总的来说,都是属于节点,但是具体类型不同。
1.3 关系
- 祖先 —— 包含后代元素的元素是祖先元素
- 后代 —— 被祖先元素包含的元素是后代元素
- 父 —— 直接包含子元素的元素是父元素
- 子 —— 直接被父元素包含的元素是子元素
- 兄弟 —— 拥有相同父元素的元素是兄弟元素
1.4 HelloWorld
要使用DOM来操作网页,我们需要浏览器至少得先给我一个对象,才能去完成各种操作
所以浏览器已经为我们提供了一个document对象,它是一个全局变量可以直接使用document
代表的是整个的网页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="btn">点我</button>
<script>
// 通过 id 获取按钮对象
const btn = document.getElementById("btn")
console.log(btn) // 打印按钮对象
console.dir(btn) // 打印该按钮的所有属性
btn.innerText = "Hello World" // 修改按钮文字
</script>
</body>
</html>
1.5 document对象
- document对象表示的是整个网页
- document对象的原型链:
HTMLDocument -> Document -> Node -> EventTarget -> Object.prototype -> null
- 凡是在原型链上存在的对象的属性和方法都可以通过Document去调用
- 部分属性:
document.documentElement
-->html
根元素document.head
-->head
元素document.title
-->title
元素document.body
-->body
元素document.links
--> 获取页面中所有的超链接
2. DOM节点
2.1 元素节点
- 元素节点对象(element)
- 在网页中,每一个标签都是一个元素节点
- 如何获取元素节点对象?
- 通过document对象来获取元素节点
- 通过document对象来创建元素节点
2.1.1 获取已有的元素节点
const btn = document.getElementById("btn")
const spans = document.getElementsByClassName("s1")
const divs = document.getElementsByTagName("div")
const genderInput = document.getElementsByName("gender")
const divs2 = document.querySelectorAll("div")
const div = document.querySelector("div")
const h2 = document.createElement("h2")
console.log(spans)
for(let i=0; i<spans.length; i++){
alert(spans[i])
spans[i].innerText = "哈哈哈"+i
}
2.1.1 document.getElementById()
根据id获取一个元素节点对象
2.1.2 document.getElementsByClassName()
- 根据元素的class属性值获取一组元素节点对象
- 返回的是一个类数组对象
- 该方法返回的结果是一个实时更新的集合
- 当网页中新添加元素时,集合也会实时的刷新
2.1.3 document.getElementsByTagName()
- 根据标签名获取一组元素节点对象
- 返回的结果是可以实时更新的集合
2.1.4 document.getElementsByTagName()
获取页面中所有的元素
2.1.5 document.getElementsByName()
- 根据name属性获取一组元素节点对象
- 返回一个实时更新的集合
- 主要用于表单项
2.1.6 document.querySelectorAll()
- 根据选择器去页面中查询元素
- 会返回一个类数组(不会实时更新)
2.1.7 document.querySelector()
根据选择器去页面中查询第一个符合条件的元素
2.1.8 document.createElement()
创建一个元素节点
根据标签名创建一个元素节点对象
2.1.2 通过某个元素获取其他节点
div元素的原型链
通过元素节点对象获取其他节点的方法
element.childNodes
:获取当前元素的子节点(会包含空白的子节点)element.children
:获取当前元素的子元素,不包含文本节点,使用更多一些element.firstElementChild
:获取当前元素的第一个子元素element.lastElementChild
:获取当前元素的最后一个子元素element.nextElementSibling
:获取当前元素的下一个兄弟元素element.previousElementSibling
:获取当前元素的前一个兄弟元素element.parentNode
:获取当前元素的父节点element.tagName
:获取当前元素的标签名
2.2 文本节点
在DOM中,网页中所有的文本内容都是文本节点对象,可以通过元素来获取其中的文本节点对象,但是我们通常不会这么做
我们可以直接通过元素去修改其中的文本,修改文本的三个属性
element.textContent
- 获取或修改元素中的文本内容
- 获取的是标签中的内容,不会考虑css样式,所有的原始文本,包括换行和空格
element.innerText
- 获取或修改元素中的文本内容
- innerText获取内容时,会考虑css样式
- 例如设置为
display: none
通过innerText就获取不到文本,通过textContent就可以获取到文本
- 例如设置为
- 通过innerText去读取CSS样式,会触发网页的重排(计算CSS样式)
- 当字符串中有标签时,会自动对标签进行转义:
<li>
--><li>
,所以在网页上还是会原样显示出来添加的文本
element.innerHTML
- 获取或修改元素中的html代码
- 可以直接向元素中添加html代码
- innerHTML插入内容时,有被xss注入的风险
2.3 属性节点(Attr)
- 在DOM也是一个对象,通常不需要获取对象而是直接通过元素即可完成对其的各种操作
- 如何操作属性节点:
-
方式一:
- 读取:元素.属性名(注意,class属性需要使用
className
来读取),读取一个布尔值时,会返回true或false - 修改:元素.属性名 = 属性值
- 读取:元素.属性名(注意,class属性需要使用
-
方式二:
- 读取:
元素.getAttribute(属性名)
- 修改:
元素.setAttribute(属性名, 属性值)
- 删除:
元素.removeAttribute(属性名)
- 读取:
-
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input class="a" type="text" name="username" value="admin">
<script>
const input = document.getElementsByName("username")[0]
// const input = document.querySelector("[name=username]")
console.log(input.getAttribute("type"))
input.setAttribute("value", "孙悟空")
input.setAttribute("disabled", "disabled")
</script>
</body>
</html>
2.4 事件
事件(event)
-
事件就是用户和页面之间发生的交互行为
- 比如:点击按钮、鼠标移动、双击按钮、敲击键盘、松开按键…
-
可以通过为事件绑定响应函数(回调函数),来完成和用户之间的交互
-
绑定响应函数的方式:
-
可以直接在元素的属性中设置
-
可以通过为元素的指定属性设置回调函数的形式来绑定事件(一个事件只能绑定一个响应函数)
-
可以通过元素
addEventListener()
方法来绑定事件
-
2.4.1 直接在元素属性中设置
<button onclick="alert(123)"></button>
<button onclick="btnClick()"></button>
<script>
function btnClick(){
console.log("button click")
}
</script>
2.4.2 设置回调函数
<button id="btn"></button>
<script>
const btn = document.getElementById("btn")
// 为按钮对象的事件属性设置响应函数
btn.onclick = function(){
console.log("123")
}
// 一个时间只能绑定一个函数,不能重复绑定,后续会覆盖掉前面的函数
btn.onclick = function(){
console.log("1123111")
}
</script>
2.4.3 元素.addActionListener()
<button id="btn"></button>
<script>
const btn = document.getElementById("btn")
// 为按钮对象的事件属性设置响应函数
btn.addEventListener("click", function () {
console.log("哈哈哈")
})
// 可以同时绑定多个函数,先绑定谁就先执行谁
btn.addEventListener("click", function () {
console.log("嘻嘻嘻")
})
btn.addEventListener("click", function () {
console.log("呜呜呜")
})
</script>
2.5 文档的加载
网页是自上向下加载的,如果将js代码编写到网页的上边,js代码在执行时,网页还没有加载完毕,这时会出现无法获取到DOM对象的情况
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
const btn = document.querySelector("#btn")
console.log(btn)
</script>
</head>
<body>
<button id="btn">按钮</button>
</body>
</html>
上述代码在打印的时候就会打印出来null
,表示无法获取到button元素
如果将script移动到button之后,就可以正常打印出结果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="btn">按钮</button>
<script>
const btn = document.querySelector("#btn")
console.log(btn)
</script>
</body>
</html>
为了保证在任意位置写的代码均生效我们可以使用下面两个函数
- window.onload 事件会在窗口中的内容加载完毕之后才触发
- document的DOMContentLoaded事件会在当前文档加载完毕之后触发
例如引入iframe之后,window.onload会在iframe全部加载完成之后才会执行,document会在当前文档加载完成之后执行,而不会等待另一个iframe加载完成之后才执行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
window.onload = () => {
const btn = document.querySelector("#btn")
console.log(btn)
}
</script>
</head>
<body>
<button id="btn">按钮</button>
</body>
</html>
如何解决这个问题:
-
将script标签编写到body的最后
-
将代码编写到window.onload的回调函数中
-
将代码编写到document对象的DOMContentLoaded的回调函数中(执行时机更早)
-
将代码编写到外部的js文件中,然后以defer的形式进行引入(执行时机更早,早于DOMContentLoaded)
<script defer src="./script.js"></script>
2.6 练习
2.6.1 切换图片
实现点击切换图片,并实现循环切换
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<img id="image" width="400" height="400"/>
<div id="info"></div>
<div>
<button id="pre">上一张</button>
<button id="next">下一张</button>
</div>
<script>
let imageIdx = 0
imageName = [
"./images/1.png",
"./images/2.png",
"./images/3.png",
"./images/4.png",
"./images/5.png",
]
const img = document.querySelector("#image")
img.src = imageName[0]
const infoDiv = document.querySelector("#info")
infoDiv.innerText = `共${imageName.length}张图片,当前第${imageIdx + 1}张`
const preBtn = document.querySelector("#pre")
const nextBtn = document.querySelector("#next")
preBtn.addEventListener("click", () => {
imageIdx = (imageIdx - 1 + imageName.length) % imageName.length
img.src = imageName[imageIdx]
infoDiv.innerText = `共${imageName.length}张图片,当前第${imageIdx + 1}张`
})
nextBtn.addEventListener("click", () => {
imageIdx = (imageIdx + 1) % imageName.length
img.src = imageName[imageIdx]
infoDiv.innerText = `共${imageName.length}张图片,当前第${imageIdx + 1}张`
})
</script>
</body>
</html>
2.6.2 实现按钮之间相互配合
- 全选:选中所有按钮
- 取消:取消选中所有按钮
- 反选:选中则取消,取消则选中
- 提交:显示出所有爱好
- 如果下面所有标签都选中了则全选也需要被选中;如果有一个没有被选中,则全选不能被选中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<form action="#">
<div>
请选择你的爱好:
<input type="checkbox" id="check-all"/> 全选
</div>
<div>
<input type="checkbox" name="hobby" value="乒乓球"/> 乒乓球
<input type="checkbox" name="hobby" value="篮球"/> 篮球
<input type="checkbox" name="hobby" value="羽毛球"/> 羽毛球
<input type="checkbox" name="hobby" value="足球"/> 足球
</div>
<div>
<button type="button" id="all">全选</button>
<button type="button" id="no">取消</button>
<button type="button" id="reverse">反选</button>
<button type="button" id="send">提交</button>
</div>
</form>
</div>
<script>
const hobbies = document.querySelectorAll("[name=hobby]")
const checkAll = document.querySelector("#check-all")
// 检查是否有没有被选中的标签
function checkAllSelect() {
for (let hobby of hobbies) {
if (!hobby.checked) {
checkAll.checked = false
return
}
}
checkAll.checked = true
}
hobbies.forEach(item => {
item.onchange = checkAllSelect
})
// 全选
const selectAllBtn = document.querySelector("#all")
selectAllBtn.onclick = () => {
hobbies.forEach(item => {
item.checked = true
})
checkAll.checked = true
}
// 取消全选
const selectNoBtn = document.querySelector("#no")
selectNoBtn.onclick = () => {
hobbies.forEach(item => {
item.checked = false
})
checkAll.checked = false
}
// 反选
const reverseBtn = document.querySelector("#reverse")
reverseBtn.onclick = () => {
hobbies.forEach(item => {
item.checked = !item.checked
})
checkAllSelect()
}
// 提交
const submitBtn = document.querySelector("#send")
submitBtn.onclick = () => {
let res = []
hobbies.forEach(item => {
if (item.checked) {
console.log(item)
res.push(item.value)
}
})
console.log(res)
alert("选中的兴趣: " + res.join(", "))
}
</script>
</body>
</html>
3. DOM的修改
3.1 添加节点
document.createElement(tagName)
:创建一个标签document.appendChild()
:用于给一个节点添加子节点document.insertAdjacentElement()
:可以向元素的任意位置添加元素- 两个参数:1.要添加的位置 2.要添加的元素
- beforeend 标签的最后
- afterbegin 标签的开始
- beforebegin 在元素的前边插入元素(兄弟元素)
- afterend 在元素的后边插入元素(兄弟元素)
docuemnt.insertAdjacentHTML()
:向任意位置添加HTML文本
// 创建一个li
const li = document.createElement("li")
// 向li中添加文本
li.textContent = "唐僧"
// 给li添加id属性
li.id = "ts"
const list = document.getElementById("list")
// 下面三种方法插入的位置都是一样的
list.appendChild(li)
// list.insertAdjacentElement("beforeend", li)
// list.insertAdjacentHTML("beforeend", "<li id='ts'>唐僧</li>")
3.2 删除节点
document.replaceWith()
:使用一个元素来替换当前元素document.remove()
:删除当前元素
const li = document.createElement("li")
li.textContent = "蜘蛛精"
li.id = "zzj"
// 获取swk
const swk = document.getElementById("swk")
// replaceWith() 使用一个元素替换当前元素
swk.replaceWith(li)
// remove()方法用来删除当前元素
// swk.remove()
3.3 练习
实现表格的增删
- 删除:删除一整行数据
- 增加:将下方填入信息加入表格中
注意事项
-
a标签的跳转
- 在a标签的href中设置为#,依然会发生跳转,#表示跳转到页面开头
- 在a标签的onclick事件最后返回false就可以取消默认的跳转行为,但是这种方式只在
xxx.xxx = function(){}
这种方式绑定的事件中才适用 - 也可设置为
javascript:;
改为执行这段代码,也可以取消默认跳转
-
form中的button中type设置为button可以取消默认的提交事件
-
这种写法,容易被xss的攻击,当用户在姓名地方写入
<script src="xxx"></srcipt>
,然后我们将这个数据存入到数据库中,在下个用户加载页面的时候就会自动执行这个脚本,风险很大,所以,当要渲染用户自己输入的内容的话,使用xxx.innerText = xx
修改,这种方法会自动转移其中的字符const tbody = document.querySelector("tbody") tbody.insertAdjacentHTML( "beforeend", ` <tr> <td>${name}</td> <td>${email}</td> <td>${salary}</td> <td><a href="javascript:;">删除</a></td> </tr> ` )
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.outer {
width: 400px;
margin: 100px auto;
text-align: center;
}
table {
width: 400px;
border-collapse: collapse;
margin-bottom: 20px;
}
td,
th {
border: 1px black solid;
padding: 10px 0;
}
form div {
margin: 10px 0;
}
</style>
</head>
<body>
<div class="outer">
<table>
<tbody>
<tr>
<th>姓名</th>
<th>邮件</th>
<th>薪资</th>
<th>操作</th>
</tr>
<tr>
<td>孙悟空</td>
<td>swk@hgs.com</td>
<td>10000</td>
<td><a href="javascript:;">删除</a></td>
</tr>
<tr>
<td>猪八戒</td>
<td>zbj@glz.com</td>
<td>8000</td>
<td><a href="javascript:;">删除</a></td>
</tr>
<tr>
<td>沙和尚</td>
<td>shs@lsh.com</td>
<td>6000</td>
<td><a href="javascript:;">删除</a></td>
</tr>
</tbody>
</table>
<form action="#">
<div>
<label for="name">姓名</label>
<input type="text" id="name"/>
</div>
<div>
<label for="email">邮件</label>
<input type="email" id="email"/>
</div>
<div>
<label for="salary">薪资</label>
<input type="number" id="salary"/>
</div>
<button>添加</button>
</form>
</div>
<script>
// 删除
function removeTr() {
console.log(this)
console.log(this.parentNode)
this.parentNode.parentNode.remove()
}
const removeLinks = document.querySelectorAll("a")
removeLinks.forEach(item => {
item.onclick = removeTr
})
// 添加
const addBtn = document.querySelector("button")
addBtn.onclick = function () {
const name = document.querySelector("#name").value
const email = document.querySelector("#email").value
const salary = document.querySelector("#salary").value
const tbody = document.querySelector("tbody")
const tr = document.createElement("tr")
const td1 = document.createElement("td")
td1.innerText = name
const td2 = document.createElement("td")
td2.innerText = email
const td3 = document.createElement("td")
td3.innerText = salary
tr.appendChild(td1)
tr.appendChild(td2)
tr.appendChild(td3)
tbody.appendChild(tr)
tr.insertAdjacentHTML("beforeend", "<td><a href='javascript:;' >删除</a></td>")
document.links[document.links.length - 1].onclick = removeTr
}
</script>
</body>
</html>
3.4 复制节点
document.cloneNode()
:对节点进行复制时,它会复制节点的所有特点包括各种属性- 这个方法默认只会复制当前节点,而不会复制节点的子节点
- 可以传递一个true作为参数,这样该方法也会将元素的子节点一起复制
下面案例演示将一个li节点从一个list复制到另一个list中
如果直接使用appendChild的话则id为l1的标签会在第一个ul中消失
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button id="btn01">点我一下</button>
<ul id="list1">
<li id="l1">孙悟空</li>
<li id="l2">猪八戒</li>
<li id="l3">沙和尚</li>
</ul>
<ul id="list2">
<li>蜘蛛精</li>
</ul>
<script>
/* 点击按钮后,将id为l1的元素添加list2中 */
const list2 = document.getElementById("list2")
const l1 = document.getElementById("l1")
const btn01 = document.getElementById("btn01")
btn01.onclick = function () {
const newL1 = l1.cloneNode(true) // 用来对节点进行复制的
newL1.id = "newL1"
list2.appendChild(newL1)
}
</script>
</body>
</html>
3.5 修改CSS样式
- 通过
元素.style.样式名 = 样式值
修改element的样式- 会直接在元素的内联样式中添加对应样式
- 如果样式名中含有
-
,则需要将样式表修改为驼峰命名法 - background-color --> backgroundColor
案例:点击按钮后,修改box1的宽度
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Document</title>
<style>
.box1 {
width: 200px;
height: 200px;
background-color: #bfa;
}
</style>
</head>
<body>
<button id="btn">点我一下</button>
<hr/>
<div class="box1"></div>
<script>
const btn = document.getElementById("btn")
const box1 = document.querySelector(".box1")
btn.onclick = function () {
// 修改box1的样式
box1.style.width = "400px"
box1.style.height = "400px"
box1.style.backgroundColor = "yellow"
}
</script>
</body>
</html>
3.6 读取CSS样式
3.6.1 getComputedStyle()
元素.style.样式名
读取到的是内联样式,如果是通过class定义的样式,则这种形式不能获取到样式
正确的方式是使用getComputedStyle()
来读取样式
-
它会返回一个对象,这个对象中包含了当前元素所有的生效的样式
-
参数:
- 要获取样式的对象
- 要获取的伪元素
-
返回值:返回的一个对象,对象中存储了当前元素的样式
-
注意:样式对象中返回的样式值,不一定能来拿来直接计算,所以使用时,一定要确保值是可以计算的才去计算,如果获取数值的话可以使用
parseInt()
处理,在处理完之后设置的时候记得再加回去
案例:点击按钮后,获取box的样式
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.box1 {
height: 200px;
background-color: #bfa;
}
.box1::before {
content: "hello";
color: red;
}
</style>
</head>
<body>
<button id="btn">点我一下</button>
<hr />
<div class="box1"></div>
<script>
/*点击按钮后,读取元素的css样式*/
const btn = document.getElementById("btn")
const box1 = document.querySelector(".box1")
btn.onclick = function () {
const styleObj = getComputedStyle(box1)
console.log(styleObj.width)
console.log(styleObj.left)
console.log(styleObj.backgroundColor)
// 修改样式,记得加单位
console.log(parseInt(styleObj.width) + 100)
box1.style.width = parseInt(styleObj.width) + 100 + "px"
// 获取伪元素样式
const beforeStyle = getComputedStyle(box1, "::before")
console.log(beforeStyle.color)
}
</script>
</body>
</html>
3.6.2 通过属性读取样式
- 获取元素内部的宽度和高度(包括内容区和内边距),返回的是数值
- 元素.clientHeight:height + 2*padding
- 元素.clientWidth:width + 2*padding
- 获取元素的可见框的大小(包括内容区、内边距和边框)
- 元素.offsetHeight:height + 2*padding + border
- 元素.offsetWidth:width + 2*padding + border
- 获取元素滚动区域的大小(包括可见和不可见的总大小)
- 元素.scrollHeight:内容区最大高度 + 内边距
- 元素.scrollWidth:内容区最大宽度 + 内边距
- 获取元素的定位父元素
- 元素.offsetParent
- 定位父元素:离当前元素最近的开启了定位的祖先元素,如果所有的元素都没有开启定位则返回body
- 获取元素相对于其定位父元素的偏移,所有父元素都没有开启定位的话则是相对于body
- 元素.offsetTop
- 元素.offsetLeft
- 获取或设置元素滚动条的偏移量(可以修改的)
- 元素.scrollTop
- 元素.scrollLeft
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box1{
width: 200px;
height: 200px;
padding: 50px;
margin: 50px;
border: 10px red solid;
background-color: #bfa;
overflow: auto;
}
#box2{
width: 100px;
height: 500px;
background-color: orange;
}
</style>
</head>
<body>
<button id="btn">点我一下</button>
<hr>
<div>
<div id="box1">
<div id="box2"></div>
</div>
</div>
<script>
const btn = document.getElementById("btn")
const box1 = document.getElementById("box1")
btn.onclick = function(){
console.log(box1.clientHeight) // 300
console.log(box1.clientWidth) // 283(还有一部分被滚动条占了)
console.log(box1.offsetHeight) // 320
console.log(box1.offsetWidth) // 320
console.log(box1.scrollHeight) // 600
console.log(box1.scrollWidth) // 283
console.log(box1.offsetParent) // body
console.log(box1.offsetLeft) // 58
console.log(box1.offsetTop) // 92
console.log(box1.scrollTop) // 0
box1.scrollTop = 100
console.log(box1.scrollTop) // 100
}
</script>
</body>
</html>
3.7 修改class
如果直接通过js代码中修改某个样式,会造成代码耦合太高,我们可以通过修改class属性来间接的修改样式
通过class修改样式的好处:
- 可以一次性修改多个样式
- 对JS和CSS进行解耦
const box1 = document.querySelector(".box1")
box1.className += " box2"
元素.classList
是一个对象,对象中提供了对当前元素的类的各种操作方法
元素.classList.add()
向元素中添加一个或多个class,如果有的话则不会做任何操作元素.classList.remove()
移除元素中的一个或多个class元素.classList.toggle()
切换元素中的class元素.classList.replace()
替换class元素.classList.contains()
检查class
案例:点击按钮后,修改box1的宽度
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Document</title>
<style>
.box1 {
width: 200px;
height: 200px;
background-color: #bfa;
}
.box2 {
background-color: yellow;
width: 300px;
height: 500px;
border: 10px greenyellow solid;
}
</style>
</head>
<body>
<button id="btn1">add</button>
<button id="btn2">toggle</button>
<hr/>
<div class="box1 box3 box4"></div>
<script>
const btn1 = document.getElementById("btn1")
const btn2 = document.getElementById("btn2")
const box1 = document.querySelector(".box1")
btn1.onclick = function () {
box1.classList.add("box2", "box3")
let result = box1.classList.contains("box2")
console.log(result)
}
btn2.onclick = function () {
box1.classList.toggle("box2")
}
</script>
</body>
</html>
4. 事件
4.1 事件对象简介
- 事件对象是由浏览器在事件触发时所创建的对象,这个对象中封装了事件相关的各种信息
- 通过事件对象可以获取到事件的详细信息比如:鼠标的坐标、键盘的按键…
- 浏览器在创建事件对象后,会将事件对象作为响应函数的参数传递,所以我们可以在事件的回调函数中定义一个形参来接收事件对象
案例:获取鼠标的坐标
const box1 = document.getElementById("box1")
// 下面两种方式都可以拿到事件对象
// box1.onmousemove = event => {
// console.log(event)
// }
box1.addEventListener("mousemove", event => {
console.log(event.clientX, event.clientY)
box1.textContent = event.clientX + "," + event.clientY
})
4.2 Event对象
Event - Web API 接口参考
- 在DOM中存在着多种不同类型的事件对象,多种事件对象有一个共同的祖先 Event
- event.target 触发事件的对象
- event.currentTarget 绑定事件的对象(同this)
- event.stopPropagation() 停止事件的传导
- event.preventDefault() 取消默认行为
- 事件的冒泡(bubble)
- 事件的冒泡就是指事件的向上传导
- 当元素上的某个事件被触发后,其祖先元素上的相同事件也会同时被触发
- 冒泡的存在大大的简化了代码的编写,但是在一些场景下我们并不希望冒泡存在不希望事件冒泡时,可以通过事件对象来取消冒泡
案例:阻止事件冒泡
- 点击最小的div不会向外冒泡,点击中间的div会向外冒泡
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Document</title>
<style>
#box1 {
width: 300px;
height: 300px;
background-color: greenyellow;
}
#box2 {
width: 250px;
height: 250px;
background-color: #ff0;
}
#box3 {
width: 200px;
height: 200px;
background-color: orange;
}
</style>
</head>
<body>
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
const box1 = document.getElementById("box1")
const box2 = document.getElementById("box2")
const box3 = document.getElementById("box3")
box1.addEventListener("click", function (event) {
alert("Hello 我是box1")
})
box2.addEventListener("click", function (event) {
alert("我是box2")
})
box3.addEventListener("click", function (event) {
event.stopPropagation() // 取消事件的传到
alert("我是box3")
})
</script>
</body>
</html>
案例:取消超链接的默认跳转行为
const link = document.querySelector("a")
link.addEventListener("click", (event) => {
event.preventDefault() // 取消默认行为
alert("被点了~~~")
})
4.3 冒泡
- 取消冒泡:
event.stopPropagation()
案例:图标跟随鼠标
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#box {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #8cdb69;
position: absolute;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
const box = document.querySelector("#box")
document.addEventListener("mousemove", (event) => {
box.style.top = event.y - 10 + 'px'
box.style.left = event.x - 10 + 'px'
})
</script>
</body>
</html>
案例:在一片区域中图标不跟随鼠标(使用阻止冒泡事件实现效果)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#box {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #8cdb69;
position: absolute;
}
#box2 {
width: 400px;
height: 400px;
background-color: black;
}
</style>
</head>
<body>
<div id="box"></div>
<div id="box2"></div>
<script>
const box = document.querySelector("#box")
const box2 = document.querySelector("#box2")
document.addEventListener("mousemove", (event) => {
box.style.top = event.y + 'px'
box.style.left = event.x + 'px'
})
box2.addEventListener("mousemove", (event) => {
event.stopPropagation()
})
</script>
</body>
</html>
4.4 事件的委派
委派就是将本该绑定给多个元素的事件,统一绑定给父级元素,这样可以降低代码复杂度方便维护
案例:点击li标签,打印出其中的内容
思路:原来是通过在每一个li上都绑定一个点击事件,然后对新加的li也再添加事件,但是现在可以使用事件的委派来实现这个操作,也就是直接绑定在父级元素上
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
ul {
background-color: orange;
width: 100px;
}
</style>
</head>
<body>
<ul>
<li>链接1</li>
<li>链接2</li>
<li>链接3</li>
<li>链接4</li>
</ul>
<script>
const ul = document.querySelector("ul")
ul.addEventListener("click", event => {
alert(event.target.innerText)
})
</script>
</body>
</html>
4.5 事件的捕获
事件的传播机制
- 在DOM中,事件的传播可以分为三个阶段:
- 捕获阶段 (由祖先元素向目标元素进行事件的捕获)(默认情况下,事件不会在捕获阶段触发)
- 目标阶段 (触发事件的对象)
- 冒泡阶段 (由目标元素向祖先元素进行事件的冒泡)
- 事件的捕获,指事件从外向内的传导,当前元素触发事件以后,会先从当前元素最大的祖先元素开始向当前元素进行事件的捕获
- 如果希望在捕获阶段触发事件,可以将addEventListener的第三个参数设置为true
- 一般情况下我们不希望事件在捕获阶段触发,所有通常都不需要设置第三个参数
通过
event.eventPhase
可以获得事件触发的阶段1 捕获阶段 2 目标阶段 3 冒泡阶段
- 捕获:由外向里传播
- 冒泡:由里向外传播
- 使用
stopPropagation()
可以停止捕获或者冒泡,在执行到的函数停止
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
#box1 {
width: 300px;
height: 300px;
background-color: greenyellow;
}
#box2 {
width: 200px;
height: 200px;
background-color: orange;
}
#box3 {
width: 100px;
height: 100px;
background-color: tomato;
}
</style>
</head>
<body>
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
const box1 = document.getElementById("box1")
const box2 = document.getElementById("box2")
const box3 = document.getElementById("box3")
box1.addEventListener("click", event => {
alert("1" + event.eventPhase) // eventPhase 表示事件触发的阶段
//1 捕获阶段 2 目标阶段 3 冒泡阶段
})
box2.addEventListener("click", event => {
alert("2" + event.eventPhase)
})
box3.addEventListener("click", event => {
alert("3" + event.eventPhase)
})
</script>
</body>
</html>
5. 其他
5.1 定时器
通过定时器,可以使代码在指定时间后执行
5.1.1 单次定时器
setTimeout()
- 参数:
- 回调函数(要执行的代码)
- 间隔的时间(毫秒)
- 关闭定时器:
clearTimeout(标识)
const timer = setTimeout(()=>{
alert("我是定时器中的代码")
}, 3000)
clearTimeout(timer)
5.1.2 多次定时器
setInterval()
(每间隔一段时间代码就会执行一次)
- 参数:
- 回调函数(要执行的代码)
- 间隔的时间(毫秒)
- 关闭定时器:
clearInterval()
let num = 0
const timer = setInterval(() => {
num++
numH1.textContent = num
if(num === 200){
clearInterval(timer)
}
}, 30)
5.1.3 原理
定时器的原理是在到时之后将定时器的回调函数放到消息队列中,等调用栈中的函数执行完成之后才去执行
通过以下代码就可以看到,定时器并不是3000ms倒计时结束就立即执行的
console.time()
setTimeout(function(){
console.timeEnd()
console.log("定时器执行了~")
}, 3000)
使程序卡6s
const begin = Date.now()
while (Date.now() - begin < 6000) {}
setInterval()
是每隔固定时间将函数放到消息队列汇总,如果函数的执行速度比较慢,则无法确保每一次的执行间隔是一样的,通过下面这种方式可以确保每次执行都有相同的间隔
console.time("间隔")
setTimeout(function fn() {
console.timeEnd("间隔")
alert("哈哈")
console.time("间隔")
// 在setTimeout的回调函数的最后,在调用一个setTimeout
setTimeout(fn, 3000)
}, 3000)
5.2 事件循环
事件循环(event loop)
- 函数在每次执行时,都会产生一个执行环境
- 执行环境负责存储函数执行时产生的一切数据
- 问题:函数的执行环境要存储到哪里呢?
- 函数的执行环境存储到了一个叫做调用栈的地方
- 栈,是一种数据结构,特点 后进先出
- 调用栈(call stack)
- 调用栈负责存储函数的执行环境
- 当一个函数被调用时,它的执行环境会作为一个栈帧,插入到调用栈的栈顶,函数执行完毕其栈帧会自动从栈中弹出
对于下列代码,执行到fn2()函数内部时调用栈如图,当执行玩fn2()之后会将fn2对应栈帧从调用栈中弹出,直到执行完所有代码
5.3 消息队列
- 消息队列负责存储将要执行的函数
- 当我们触发一个事件时,其响应函数并不是直接就添加到调用栈中的,因为调用栈中有可能会存在一些还没有执行完的代码
- 事件触发后,JS引擎是将事件响应函数插入到消息队列中排队
当我们点击页面中的按钮的时候,对应的点击事件会先加入到消息队列中,等待调用栈中的所有代码全部执行完了之后,再从消息队列中取出第一个事件到调用栈中执行,直到消息队列为空,然后后面会定期扫描消息队列,如果消息队列中有新消息,则会继续执行
调用栈先于消息队列执行的证据:
下面这段代码,setTimeout()
0ms后就应该执行,说明就是立即执行的,但是实际运行结果却是2222先打印
setTimeout(() => {
console.log(11111)
}, 0)
console.log(222222)