前端基础学习之JavaScript part four

Posted by OuterCyrex on July 29, 2024

四.DOM

DOMDocument 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选择器获取

上述的方法虽然多样,但对我们而言自由度不足,如果我们需要寻找非IdClass等的标签时,可能会较为麻烦。

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.标签内容

首先介绍三个属性:innerTextinnerHTMLouterHTML

属性 作用
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>

除此之外,如果第二个值,即Nodenull,则效果与append一致,插入最后一个子节点之后。


此处介绍一下如何获取一个节点的父节点子节点

通过dom获取的节点具有两个属性:parentchildren

前者会返回当前节点的父节点,后者会返回当前节点的子节点列表

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.添加兄弟结点

添加兄弟结点的操作涉及两个方法:beforeafter

方法 作用
before 在对应的子节点之前插入该结点
after 在对应的子节点之后插入该结点

还是以之前的boxdom标签为例:

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.删除节点

删除节点由两个方法可以进行调用,分别是removeChildremove

方法 作用
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

同步任务先完成因此15先被执行,之后是微任务Promise宏任务setTimeout,将二者的返回值压入事件队列,此时事件队列便有同步任务42微任务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

上述代码也同理,便于我们理解事件队列运作原理