四.DOM
DOM是Document Object Model
的首字母缩写,即文档对象模型。
其允许开发人员进行获取、添加、修改和移除页面内特定元素的操作。
一.获取元素
下列内容介绍了一些获取元素的方法。注意,如果未获取到对应元素,下述方法均会返回null
值。
1.ID获取
通过ID
获取主要是通过document
类中的getElementById
方法来实现,其会返回该标签的内容。
1
2
3
4
const dom = document.getElementById("box")
console.log(dom)
//返回值
<div id="box"></div>
2.Class获取
与ID获取一致,Class
获取只需要使用getElementsByClassName
即可,但注意,由于Class
的标签并不唯一,因此该方法会返回一个数组。
1
2
3
4
5
6
7
8
const dom = document.getElementsByClassName("box")
console.log(dom)
//输出结果
HTMLCollection(2) [div.box, div.box]
0: div.box
1: div.box
length: 2
[[Prototype]]: HTMLCollection
3.其他获取方法
除了上述介绍的两种获取方法外,还有以下几种获取方法:
方法 | 作用 |
---|---|
getElementById | 根据id来获取对应标签 |
getElementsByClassName | 根据类来获取对应标签,返回值为数组 |
getElementsByTagName | 根据标签名来获取对应标签,返回值为数组 |
getElementsByName | 根据标签的name 字段来获取对应标签,返回值为数组 |
getElementsByTagNameNS | 在提供的URL内根据标签离开获取对应标签,返回值为数组,语法为getElementsByTagNameNS("URL",Tag) |
4.CSS选择器获取
上述的方法虽然多样,但对我们而言自由度不足,如果我们需要寻找非Id
非Class
等的标签时,可能会较为麻烦。
在DOM
中提供了一种根据CSS
选择器来获取对应标签的方法,确保所有标签都能被查找。
现假定存在下述HTML
标签:
1
2
3
4
5
<div id="ID"></div>
<input name="Outer">
<div class="box"></div>
<div class="box"></div>
<article></article>
我们便可以根据其对应的CSS
选择器进行查找
1
2
3
4
5
6
7
8
9
console.log(document.querySelector("#ID"))
console.log(document.querySelector("input[name='Outer']"))
console.log(document.querySelector(".box"))
console.log(document.querySelector("article"))
//返回值
<div id="ID"></div>
<input name="Outer">
<div class="box"></div>
<article></article>
注意,这里存在两个方法:
方法 | 作用 |
---|---|
querySelector | 返回当前HTML中第一个满足该选择器的标签 |
querySelectorAll | 获取当前HTML中所有满足该选择器的标签并返回一个数组 |
二.元素对象
通过上述方法获取的标签可以被称为元素对象。在DOM
其带有一系列属性可以供我们获取。
以<div id="ID">你好</div>
为例:
1
2
3
4
5
6
7
console.log(document.querySelector("#ID").tagName)
console.log(document.querySelector("#ID").id)
console.log(document.querySelector("#ID").textContent)
//输出结果
DIV
ID
你好
除了上述的三种属性,还有很多其他的属性可以调用,在此不做赘述。
1.属性操作
获取了标签之后,我们可以对该标签的属性进行获取、添加与修改。
假定存在标签<a href="http://outercyrex.github.io/"></a>
。
我们可以通过getAttribute
来获取属性
1
2
3
4
const dom = document.querySelector("a")
console.log(dom.getAttribute("href"))
//输出结果
http://outercyrex.github.io/
通过setAttribute
来修改属性
1
2
3
4
5
const dom = document.querySelector("a")
dom.setAttribute("href","www.baidu.com")
console.log(document.querySelector("a"))
//输出结果
<a href="www.baidu.com"></a>
最后可以通过removeAttribute
来删除属性
1
2
3
4
5
const dom = document.querySelector("a")
dom.removeAttribute("href")
console.log(document.querySelector("a"))
//输出结果
<a></a>
2.dataset
如果一个标签的属性过多,可能会存在不易管理的情况,如果我们可以将所有属性按照对象的形式更改,可以提升管理效率。data
关键字可以帮我们实现这种操作。
在一个标签的对应属性上加上data
,可以将其变为data
属性
1
<div data-name="Outer" data-box="newBox"></div>
在此以上述的标签为例:
1
2
3
4
5
6
7
8
9
10
const dom = document.getElementById("1")
console.log(dom.dataset)
console.log(dom.dataset.name)
console.log(dom.dataset.box)
console.log(dom.dataset["name"])
//输出结果
DOMStringMap {name: 'Outer', box: 'newBox'}
Outer
newBox
Outer
如上述代码的返回结果可知,dataset
会将对应标签的data
属性转化为一个对象,我们可以通过访问对象的方法对其进行查询、添加、修改和删除。
1
2
3
4
5
const dom = document.getElementById("1")
dom.dataset.date = new Date().toString()
console.log(dom)
//输出结果
<div id="1" data-name="Outer" data-box="newBox" data-date="Sun Jul 28 2024 21:33:29 GMT+0800 (中国标准时间)"></div>
删除则使用delete
操作即可,与对象的操作一致。
3.classList
对于类的操作,dom
提供了classList
来快捷的操作一个标签的类属性。
假定有一个class = "box"
的<div>
标签,则:
1
2
3
4
5
const dom = document.getElementsByClassName("box")
const list = dom[0].classList
console.log(list)
//输出结果
DOMTokenList ['box', value: 'box']
classList
为我们提供了多种操作
方法 | 作用 |
---|---|
classList.add() | 为该标签的class 属性增加新的值 |
classList.contains() | 判断当前标签的class 属性内是否含有对应的字段,返回布尔值 |
classList.toggle() | 若当前标签的class 属性含有对应的字段则将其删除,若没有则添加该字段。 |
classList.remove() | 删除对应标签的class 属性中对应的字段 |
classList.replace() | 将对应标签的class 属性中对应的字段修改为指定内容,语法为replace("旧字段","新字段") |
4.标签内容
首先介绍三个属性:innerText
、innerHTML
、outerHTML
属性 | 作用 |
---|---|
innerText | 返回当前标签内部的text 内容 |
innerHTML | 返回当前标签内部的全部HTML语句 |
outerHTML | innerHTML的内容加上当前标签的内容 |
以下述的HTML
文件为例
1
2
3
4
<div class="box">
你好!
<a href="www.baidu.com"></a>
</div>
其对应的属性内容为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const dom = document.getElementsByClassName("box")
console.log(dom[0].innerText)
//输出结果
你好!
console.log(dom[0].innerHTML)
//输出结果
你好!
<a href="www.baidu.com"></a>
console.log(dom[0].outerHTML)
//输出结果
<div class="box">
你好!
<a href="www.baidu.com"></a>
</div>
通过这种方式,我们可以通过更改对应的innerHTML
等内容来更改对应的HTML
文件。
5.style属性
接下来介绍style
属性,dom
为我们提供了style
属性让我们可以访问当前标签的各类CSS
属性。
以下述的html
代码为示例
1
<div id="box" style="font-size:20px;background-color: blue;border:2px solid red;height:100px;width: 100%"></div>
我们可以通过style
属性来对其各个属性进行更改
1
2
3
4
5
6
7
8
const dom = document.getElementById("box")
console.log(dom.style.backgroundColor)
console.log(dom.style.fontSize)
dom.style.height = "200px"
//输出结果
blue
20px
注意,修改时传入的值应为字符串。
此外,我们可以通过getComputedStyle
来获取style
内的计算结果
1
<div id="box" style="height:calc(100% - 20px)"></div>
输出如下:
1
2
3
4
5
6
const dom = document.getElementById("box")
console.log(dom.style.height)
console.log(getComputedStyle(dom).height)
//输出结果
calc(100% - 20px)
980px
注意,上述的代码只能获取行内的CSS
设置,如果是在<header>
内的<style>
则是无法通过这种方式进行修改的。
而且document.style
后的属性是按照小驼峰命名法命名的,如background-color
应为backgroundColor
。也可以不用小驼峰命名法,而是使用[]
将对应的内容括起来,如[background-color]
。
三.节点操作
这里的节点便是HTML
中的内容,如标签,所说的操作即增删改查。
1.创建节点
通过document.createElement
操作,我们可以通过javascript
来创建标签
1
2
3
const dom = document.createElement("a")
dom.setAttribute("href","http://outercyrex.github.io")
dom.innerText = "Outer Blog"
通过上述操作,我们创建了一个标签<a>
,但此时还为将其插入HTML
内。
存在很多插入的方法,我们以appendChild
为示例:
1
2
3
4
5
6
const dom = document.createElement("a")
dom.setAttribute("href","http://outercyrex.github.io")
dom.innerText = "Outer Blog"
const box = document.getElementById("box")
box.appendChild(dom)
2.插入子节点
关于插入的操作有很多,首先介绍插入子节点,即将当前创建的标签插入某个标签的innerHTML
中。
方法 | 作用 |
---|---|
appendChild | 在父节点的最后一个子节点后插入一个节点 |
append | 在父节点的最后一个子节点后插入多个不同的节点,即可以传入多个值 |
insertBefore | 选择在父节点的第几个子节点之前插入节点,语法为insertBefore(Element,Node) |
重点介绍一下insertBefore
,示例如下:
1
2
3
4
5
6
const dom = document.createElement("a")
dom.setAttribute("href","http://outercyrex.github.io")
dom.innerText = "Outer Blog"
const box = document.getElementById("box")
box.insertBefore(dom,box.children[2])
其输出结果为:
1
2
3
4
5
6
<div id="box">
<div class="1"></div>
<div class="2"></div>
<a href="http://outercyrex.github.io">Outer Blog</a>
<div class="3"></div>
</div>
除此之外,如果第二个值,即Node
为null
,则效果与append
一致,插入最后一个子节点之后。
此处介绍一下如何获取一个节点的父节点和子节点
通过dom
获取的节点具有两个属性:parent
和children
前者会返回当前节点的父节点,后者会返回当前节点的子节点列表。
1
2
3
4
5
console.log(box.parent)
console.log(box.children)
//输出结果
undefined
[div.1, a, div.2, div.3]
这里由于box
的父节点为body
,因此返回了undefined
3.添加兄弟结点
添加兄弟结点的操作涉及两个方法:before
和after
方法 | 作用 |
---|---|
before | 在对应的子节点之前插入该结点 |
after | 在对应的子节点之后插入该结点 |
还是以之前的box
和dom
标签为例:
1
2
const box = document.getElementById("box")
box.children[0].after(dom)
输出结果为:
1
2
3
4
5
<div id="box">
<div class="1"></div><a href="http://outercyrex.github.io">Outer Blog</a>
<div class="2"></div>
<div class="3"></div>
</div>
尤其要注意的是其语法为:
1
对应的兄弟节点.after(要插入的节点)
4.删除节点
删除节点由两个方法可以进行调用,分别是removeChild
和remove
方法 | 作用 |
---|---|
removeChild() | 移除对应节点的特定子节点,传入的参数为要删除的节点。且该方法会返回被删除的节点 |
remove() | 移除使用该方法的结点 |
接下来对二者进行示例:
1
2
3
4
5
6
const removed = box.removeChild(box.children[0])
console.log(removed)
box.children[2].remove()
//输出结果
<div class="1"></div>
对应的HTML
文件:
1
2
3
<div id="box">
<div class="2"></div>
</div>
四.事件
事件,即用户或浏览器本身的某种行为,一般是用户对页面的一些动作引起的。
例如,单击某个链接或按钮、在文本框中输入文本、按下键盘上的某个按键、移动鼠标等等
当事件发生时,可以使用javascript
中的事件监听器来检测并执行某些特定的程序。
一般情况下事件的名称都是以单词on
开头的,例如点击事件onclick
、页面加载事件onload
等。
1.事件绑定
绑定事件有三种方法:
首先是标签内绑定,即在标签内写入对应的属性,如下述的oninput
属性,并让其对应<script>
中的函数。
1
2
3
4
5
6
<input placeholder="用户名" oninput="input(event)">
<script>
function input(e){
console.log(e.target.value)
}
</script>
第二种是在dom
中绑定事件,通过dom
指向对应的标签,之后进行赋值
1
2
3
4
const btn = document.querySelector("button")
btn.onclick = function(){
alert("你好")
}
最后一种也是最常见的,即添加事件监听器
1
2
3
4
5
const btn = document.querySelector("button")
const alertEvent = function(){
alert("你好")
}
btn.addEventListener("click",alertEvent)
通过这种方法设置事件监听器,我们可以随时对其移除。
1
btn.removeEventListener("click",alertEvent)
其中常见的事件见下表
事件 | 内容 |
---|---|
load | 加载成功 |
resize | 窗口大小变化 |
scroll | 滚动事件 |
焦点事件 | |
blur | 失去焦点 |
focus | 获得焦点 |
鼠标点击事件 | |
click | 用户单击鼠标左键或按下回车键触发 |
dbclick | 用户双击鼠标左键触发 |
mousedown | 在用户按下了任意鼠标按钮时触发 |
mouseenter | 在鼠标光标从元素外部首次移动到元素范围内时触发,此事件不冒泡 |
mouseleave | 元素上方的光标移动到元素范围之外时触发,不冒泡 |
mousemove | 光标在元素的内部不断的移动时触发 |
mouseover | 鼠标指针位于一个元素外部,然后用户将首次移动到另一个元素边界之内时触发 |
mouseout | 用户将光标从一个元素上方移动到另一个元素时触发 |
mouseup | 在用户释放鼠标按钮时触发 |
键盘事件 | |
keydown | 当用户按下键盘上的任意键时触发,若按住不放则会重复触发 |
keypress | 当用户按下键盘上的字符键时触发,若按住不放则会重复触发 |
keyup | 当用户释放键盘上的键时触发 |
textInput | 这是唯一的文本事件,用意是将文本显示给用户之前更容易拦截文本 |
2.事件冒泡
当一个事件被触发时,它首先在最内层的元素(也称为目标元素或事件源)上发生
然后,这个事件会向上冒泡,依次触发其父元素上的同一类型事件,一直冒泡到最外层元素,通常是document
对象
这种冒泡机制允许我们在父元素上设置事件处理器,以便在子元素的事件发生时执行特定的操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div class="p1" style="width: 50%;height:50%;background-color: red">
<div class="p2" style="width: 50%;height:50%;background-color: blue">
<div class="p3" style="width: 50%;height:50%;background-color: green">点击将触发事件冒泡</div>
</div>
</div>
<script>
const p1 = document.querySelector(".p1")
const p2 = document.querySelector(".p2")
const p3 = document.querySelector(".p3")
p3.onclick = function (e){
console.log("p3", e.target)
}
p2.onclick = function (e){
console.log("p2", e.target)
}
p1.onclick = function (e){
console.log("p1", e.target)
}
</script>
//输出结果
p3
p2
p1
此处在我们点击<div class="p3">
之后,控制台共输出了三个结果,这是由于内部的标签在触发事件时,会触发其父节点的同种事件。
或者这样想,click
了<div class="p3">
的同时其实我们也click
了其所有父节点。
如果我们想关闭这种事件冒泡,可以通过添加下述语句:
1
2
3
4
p3.onclick = function (e){
console.log("p3", e.target)
e.stopPropagation()
}
即event
类型带有一个stopPropagation()
方法,其可以阻止当前事件监听器向其父节点申请冒泡。
此外还有需要补充的是,dom
为很多事件设置了默认选项,我们可以选择更改对应的参数来关闭这些默认选项。
1
<a class="hyperText" href="http://outercyrex.github.io">Outer Blog</a>
如上述标签,如果我们点击的话便会直接跳转至对应页面。
我们可以选择增加确定的对话框。
1
2
3
4
5
6
7
const a = document.querySelector("a")
a.onclick = function (e){
const ok = confirm(`你确定要访问 ${e.target.getAttribute("href")}吗`)
if (!ok){
e.preventDefault()
}
}
其中的confirm
会阻塞代码,防止直接跳转。若我们选择否,则该事件的preventDefault()
方法会调用,关闭直接跳转功能。
3.事件委托
事件委托,又叫事件代理,即利用事件冒泡,只指定一个事件监听器,就可以管理某一类型的所有事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="box" style="width: 50%;height:50%;background-color: #96e6a1">
<div class="subBox">1</div>
<div class="subBox">2</div>
<div class="subBox">3</div>
<div class="subBox">4</div>
<div class="subBox">5</div>
<div class="subBox">6</div>
</div>
<script>
const box = document.querySelector(".box")
const click = function(){
console.log("Hello")
}
box.addEventListener("click",click)
</script>
如上述代码所示,我们每次点击.box
的子节点,都会触发其父节点的事件。这样我们其实是实现了事件委托,即只设置了父节点的事件监听器,便可对所有子节点也设置同样的属性。
4.事件循环
事件循环是一种机制,用于处理异步事件和回调函数。它是javascript
运行时环境的一部分,负责管理事件队列和调用栈。
事件循环的核心是一个事件队列,所有的事件都被放入这个队列中,然后按照顺序依次执行。如果队列为空,javascript
会等待新的任务加入队列。当javascript
代码执行时,所有同步任务都会被立即执行,而异步任务则会被放入事件队列中。
1
2
3
4
5
6
7
8
console.log(1)
console.log(2)
setTimeout(() => {
console.log(3)
},0)
console.log(4)
//输出结果
1 2 4 3
出现上述结果的原因是setTimeout
为异步任务,会等待所有同步任务结束后再进入事件队列。
更细致的进行区分的话,异步任务被分为了两种类型:宏任务和微任务。
异步任务 | 内容 |
---|---|
宏任务 | setTimeout、setInterval、I/O 操作等 |
微任务 | Promise、MutationObserver 等 |
注意宏任务和微任务的先后顺序:
当事件循环从事件队列中取出一个任务时,它会先执行所有微任务,然后再执行一个宏任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
console.log('1');
setTimeout(function() {
console.log('2');
Promise.resolve().then(function() {
console.log('3');
});
}, 0);
Promise.resolve().then(function() {
console.log('4');
});
console.log('5');
// 输出结果
1 5 4 2 3
同步任务先完成因此1
和5
先被执行,之后是微任务Promise
和宏任务setTimeout
,将二者的返回值压入事件队列,此时事件队列便有同步任务4
、2
和微任务3
,依次进行输出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
console.log('1');
setTimeout(function() {
console.log('2');
Promise.resolve().then(function() {
console.log('3');
});
}, 0);
Promise.resolve().then(function() {
console.log('4');
setTimeout(function() {
console.log('5');
}, 0);
});
console.log('6');
// 输出结果
1 6 4 2 3 5
上述代码也同理,便于我们理解事件队列的运作原理。