DOM,全称Document Object Model,中文翻译为文档对象模型。DOM属于Web API的一部分。Web API中定义了非常多的对象,通过这些对象可以完成对网页的各种操作(添加删除元素、发送请求、操作浏览器等)。


什么是DOM

DOM中的D意为Document,即文档。所谓文档就是指整个网页,换言之,DOM是用来操作网页的。O意为Object,即对象。DOM将网页中的每一部分内容都转换为了对象,div有div的对象,input有input的对象,甚至一段文本,一段注释也有其所对应的对象。转换为对象干什么?还记得面向对象吗?转换对象以后,我们就可以以面向对象的方式去操作网页,想要操作哪个元素就获取哪个元素的对象,然后通过调用其方法或属性完成各种操作。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-items.webp

>概念

节点(Node)

在DOM标准下,网页中的每一个部分都会转换为对象。这些对象有一个共同的称呼——节点(Node)。一个页面将会由多个节点构成,虽然都称为节点,但是它们却有着不同的类型:

  1. 文档节点

  2. 元素节点

  3. 文本节点

  4. 属性节点

每一个节点都有其不同的作用,文档节点表示整个网页,元素节点表示某个标签,文本节点表示网页中的文本内容,属性节点表示标签中的各种属性。如果从对象的结构上来讲,这些对象都有一个共同的父类Node。总的来说,都是属于节点,但是具体类型不同。

关系

小试牛刀

要使用DOM来操作网页,我们需要浏览器至少得先给我一个对象,才能去完成各种操作。所以浏览器已经为我们提供了一个document对象,它是一个全局变量可以直接使用,document代表的是整个的网页。

<button id="btn">点我一下</button>

<script>
// 获取btn对象
const btn = document.getElementById("btn")

// 修改btn中的文字
btn.innerText = "Click ME"
</script>

>文档节点(document)

HTMLDocument -> Document -> Node -> EventTarget -> Object.prototype -> null
document.documentElement --> html根元素
document.head --> head元素
document.title --> title元素
document.body --> body元素
document.links --> 获取页面中所有的超链接
...

>元素节点(element)

如何获取元素节点对象?

  1. 通过document对象来获取元素节点

  2. 通过document对象来创建元素节点

通过document来获取已有的元素节点:

document.getElementById()

<button id="btn">点我一下</button>
<script>
const btn = document.getElementById("btn")
console.log(btn)
</script>

document.getElementsByClassName()

<span class="s1">我是span</span>
<span class="s1">我是span</span>
<span class="s1">我是span</span>
<span class="s1">我是span</span>
<span class="s1">我是span</span>

<script>

// 返回的是一个类数组对象,该方法返回的结果是一个实时更新的集合
// 当网页中新添加元素时,集合也会实时的刷新。

const spans = document.getElementsByClassName("s1")

console.log(spans) // HTMLCollection(5) [span.s1, span.s1, span.s1, span.s1, span.s1]

 for (let i = 0; i < spans.length; i++) {
    spans[i].innerText = "我是span"+i
  }

</script>

document.getElementsByTagName()

<div>我是div</div>
<div>我是div</div>
<div>我是div</div>
<div>我是div</div>
<div>我是div</div>

<script>

// 返回的结果是可以实时更新的集合
const divs = document.getElementsByTagName("div")

// document.getElementsByTagName("*") 获取页面中所有的元素
const divs = document.getElementsByTagName("*") 

console.log(divs) // HTMLCollection(5) [div, div, div, div, div] 
</script>

document.getElementsByName()

<form>
    <input type="text" name="username">
    <input type="radio" name="gender" value="male"> 男
    <input type="radio" name="gender" value="female"> 女
</form>

<script>

// 返回一个实时更新的集合,主要用于表单项。
const genderInput = document.getElementsByName("gender")

console.log(genderInput) // NodeList(2) [input, input]
</script>

document.querySelectorAll()

<div>我是div</div>
<div>我是div</div>
<div>我是div</div>

<script>

// 会返回一个类数组(不会实时更新)
const divs2 = document.querySelectorAll("div")

console.log(divs2) // NodeList(3) [input, input,input]
</script>

document.querySelector()

<div>我是div</div>
<div>我是div</div>

<script>

// 会返回一个类数组(不会实时更新)
const div = document.querySelector("div") // .xx | #xx

console.log(divs2) // NodeList(2) [input,input]

</script>

创建一个元素节点

document.createElement()

const h2 = document.createElement("h2") // 创建了H2,但未添加

>元素的属性和方法

<div id="box1">我是box1 </div>

<script>

const box1 = document.getElementById("box1")

cosole.log(box1.__proto__)

</script>
HTMLDivElement -> HTMLElement -> Element -> Node -> ...

element.childNodes

<div id="box1">
    我是box1
    <span class="s1">我是s1</span>
    <span class="s1">我是s1</span>
</div>

<script>

const box1 = document.getElementById("box1")

const cns = box1.childNodes

console.log(children.length) // 输出:5 包含空格

</script>

element.children

<div id="box1">
    我是box1
    <span class="s1">我是s1</span>
    <span class="s1">我是s1</span>
</div>

<script>

const box1 = document.getElementById("box1")

const children = box1.children

console.log(children.length) // 输出:2(2个span)

</script>

element.firstElementChild

<p>Hello world! This is HTML5 Boilerplate.</p>
<div id="box1">
    我是box1
    <span class="s1">我是s1</span>
    <span class="s1">我是s1-2</span>
</div>
    <h1>hello,world</h1>

<script>

const box1 = document.getElementById("box1")

console.log(box1.firstElementChild) //输出: <span class="s1">我是s1</span>

</script>

element.lastElementChild

console.log(box1.lastElementChild) //输出: <span class="s1">我是s1-2</span>

element.nextElementSibling

console.log(box1.nextElementSibling) //输出: <h1>hello,world</h1>

element.previousElementSibling

console.log(box1.previousElementSibling) //输出: <p>Hello world! This is HTML5 Boilerplate.</p>

element.parentNode

console.log(box1.parentNode) //输出: <body>...</body>

element.tagName

console.log(box1.tagName) //输出: DIV

>文本节点(Text)

在DOM中,网页中所有的文本内容都是文本节点对象。

可以通过元素来获取其中的文本节点对象,但是我们通常不会这么做。

我们可以直接通过元素去修改其中的文本

修改文本的三个属性

element.textContent

<div id="box1">
    <span style="text-transform: uppercase;">我是box1</span>
</div>

<script>

// 获取的是标签中的内容,不会考虑css样式
const box1 = document.getElementById("box1")

box1.textContent = "新的内容"

</script>

element.innerText

<script>

// innerText获取内容时,会考虑css样式
// 通过innerText去读取CSS样式,会触发网页的重排(计算CSS样式)
const box1 = document.getElementById("box1")

box1.innerText = "新的内容"

// 当字符串中有标签时,会自动对标签进行转义
box1.innerText = "<li>我是li</>" //  <li> --> &lt;li&gt;

</script>

element.innerHTML

<script>

// 可以直接向元素中添加html代码
const box1 = document.getElementById("box1")

box1.innerHTML = "XXX"

// innerHTML插入内容时,有被xss注入的风险
box1.innerHTML = "<、script src='https://sss/sss.js'><\/script>"
</script>

>属性节点(Attr)

在DOM也是一个对象,通常不需要获取对象而是直接通过元素即可完成对其的各种操作。

如何操作属性节点:

方式一

读取一个布尔值时,会返回 true 或 false (如 disabled)

 <input disabled=“disabled” type="text" name="username" value="admin">

<script>

const input = document.getElementsByName("username")[0]

const input2 = document.querySelector("[name=username]")

console.log(input) // console.log(input.type)

input.name = "biu" // 修改:元素.属性名 = 属性名

</script>

方式二

 <input disabled=“disabled” type="text" name="username" value="admin">

<script>

const input = document.getElementsByName("username")[0]

const input2 = document.querySelector("[name=username]")

input.getAttribute("name") // 读取:元素.getAttribute(属性名)

input.setAttribute("name","大王") // 修改:元素.setAttribute(属性名, 属性值)
input.setAttribute("disabled",true)

input.removeAttribute("disabled") // 删除:元素.removeAttribute(属性名)

</script>

>事件(Event)

事件就是用户和页面之间发生的交互行为。例如:点击按钮、鼠标移动、双击按钮、敲击键盘、松开按键....

可以通过为时间绑定响应函数(回调函数),来完成和用户之间的交互。

绑定响应函数

直接在元素的属性中设置

<button id="btn" onclick="alert('你点我干嘛~')">点我一下</button>

为元素指定属性设置(回调函数)来绑定事件 (一个事件只能绑定一个响应函数)

<script>
// 获取到按钮对象
const btn = document.getElementById("btn")
// 为按钮对象的事件属性设置响应函数
btn.onclick = function(){
  alert("你点我干嘛~")
}
</script>

通过元素 addEventListener( )方法来绑定事件 (可绑定多个,会依次执行)

<script>
// 通过元素 addEventListener( )方法来绑定事件
btn.addEventListener("click", function(){
  alert("哈哈哈哈~")
})
</script>

>文档加载

网页是自上向下加载的,如果将 js 的代码编写到网页的上边,在 js 代码执行时,网页还没有加载完毕,这时候会出现无法获取到 DOM 对象的情况。

如何解决这个问题?

将 script 标签编写到 body 的最后

<body>

<button id="btn">点我一下</button>

<script>
  const btn = document.getElementById("btn")
  console.log(btn)
</script>

</body>

将代码编写到 window.onload 的回调函数中

<body>

<button id="btn">点我一下</button>

<script>
// window.onload 事件会在窗口中的内容加载完毕之后才触发
window.onload = function () {
    const btn = document.getElementById("btn")
    console.log(btn)
}

window.addEventListener("load", function () {
    const btn = document.getElementById("btn")
    alert(btn)
})
</script>

</body>

将代码编写到 document 对象的 DOMContentLoaded 回调函数中

<script>
// document的DOMContentLoaded事件会在当前文档加载完毕之后触发
document.addEventListener("DOMContentLoaded", function () {
    const btn = document.getElementById("btn")
    alert(btn)
})
</script>

将代码编写到外部 js 文件,以 defer 形式引入

<script defer src="./script/script.js"></script>

>Dom的修改

element.appendChild

<ul id="list">
    <li id="swk">孙悟空</li>
    <li id="zbj">猪八戒</li>
    <li id="shs">沙和尚</li>
</ul>

<button id="btn">按钮</button>

<script>
const list = document.getElementById("list")

// 获取按钮
const btn01 = document.getElementById("btn01")
btn01.onclick = function () {

    const li = document.createElement("li")
    // 向li中添加文本
    li.textContent = "唐僧"
    // 给li添加id属性
    li.id = "ts"

// 用于给一个节点添加子节点
list.appendChild(li)
</script>

element.insertAdjacentElement(position, element)

参数:

  1. beforeend 标签的最后

  2. afterbegin 标签的开始  

  3. beforebegin 在元素的前边插入元素(兄弟元素) 

  4. afterend 在元素的后边插入元素(兄弟元素)

<script>
list.insertAdjacentElement("afterend", li)
</script>

element.insertAdjacentHTML(position, text)

参数:

  1. beforeend 标签的最后

  2. afterbegin 标签的开始  

  3. beforebegin 在元素的前边插入元素(兄弟元素) 

  4. afterend 在元素的后边插入元素(兄弟元素)

<script>
list.insertAdjacentHTML("beforeend", "<li id='bgj'>白骨精</li>")
</script>

element.replaceWith

const bt = document.getElementById("btn")
btn02.onclick = function(){

    // 创建一个蜘蛛精替换孙悟空
    const li = document.createElement("li")
    li.textContent = "蜘蛛精"
    li.id = "zzj"

    // 获取swk
    const swk = document.getElementById("swk")

    // replaceWith() 使用一个元素替换当前元素
    swk.replaceWith(li)
}

element.remove

<script>
swk.remove()
</script>

>小练习

>节点的复制

cloneNode()

<!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 () {
                // 用来对节点进行复制的,true作为参数,该方法将子节点一起复制
                const newL1 = l1.cloneNode(true) 
                newL1.id = "newL1"
                list2.appendChild(newL1)
            }
        </script>
    </body>
</html>

>修改CSS样式

元素.style.样式名 = 样式值

如果样式里含有-,则需要将样式表修改为驼峰命名法

background-color --> backgroundColor

<button id="btn">点我一下</button>
<div class="box1"></div>
<script>

    const btn = document.getElementById("btn")
    const box1 = document.querySelector(".box1")

    btn.onclick = function () {
        // 修改box1的样式
        // 修改样式的方式:元素.style.样式名 = 样式值
        // 如果样式名中含有-,则需要将样式表修改为驼峰命名法
        // background-color --> backgroundColor
        box1.style.width = "400px"
        box1.style.height = "400px"
        box1.style.backgroundColor = "yellow"
    }
</script>

>读取CSS样式

getComputedStyle()

参数:

1.要获取样式的对象

2.要获取的伪元素

返回值:

返回的一个对象,对象中储存了当前元素的样式

<button id="btn">点我一下</button>
<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(parseInt(styleObj.width) + 100)
        // box1.style.width = parseInt(styleObj.width) + 100 + "px"

        // console.log(styleObj.backgroundColor)

        const beforeStyle = getComputedStyle(box1, "::before")
        // console.log(beforeStyle.color)

        console.log(box1.firstElementChild)
    }
</script

element.clientHeight / Width

element.offsetHeight / Width

element.scrollHeight / Width

element.offsetParent

当前元素最近的开启了定位的祖先元素,如果定位的元素都没有开启定位,则返回 body。

element.offsetTop / Left

element.scrollTop / Left

 <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.scrollHeight)
        // console.log(box1.offsetParent)
        console.log(box1.scrollTop)
    }

>修改操作Class

element.classList

这是一个对象,并提供了对当前元素的类的各种操作方法

element.classList.add( ) 向元素添加一个或多个Class

element.classList.remove( ) 移除元素中的一个或多个Class

element.classList.toggle( ) 切换元素的Class

element.classList.replace( ) 替换Class

element.classList.contains( ) 检查Class

<!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="btn">点我一下</button>

        <hr />

        <div class="box1 box3 box4"></div>

        <script>
            /* 
                点击按钮后,修改box1的宽度
            */
            const btn = document.getElementById("btn")
            const box1 = document.querySelector(".box1")

            btn.onclick = function () {

                // box1.className += " box2"
                // box1.classList.add("box2", "box3", "box4")
                // box1.classList.add("box1")

                // box1.classList.remove("box2")
                // box1.classList.toggle("box2")
                // box1.classList.replace("box1", "box2")
                let result = box1.classList.contains("box3")
                console.log(result) 
            }
        </script>
    </body>
</html>

>事件对象

简介(Event)

  • 事件对象就是有浏览器在事件触发时所创建的对象,这个对象中封装了时间相关的各种信息。

  • 通过事件对象可以获取到事件的详细信息,比如:鼠标的坐标、键盘的按键...

  • 浏览器在创建时间后,会将事件对象作为响应函数的参数传递,所以我们可以在事件的回调函数中定义一个形参来接收事件对象。

  • 在DOM中存在着多种不同类型的事件对象,它们有着共同的祖先,就是 Event。

<!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;
                border: 10px greenyellow solid;
            }

        </style>
    </head>
    <body>
        <div id="box1"></div>

        <script>
            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
            })
        </script>
    </body>
</html>

冒泡(bubble)

事件的冒泡就是指事件的向上传导。

当元素上的某个事件被触发后,其祖先元素上的相同事件也会被同时触发。

冒泡的存在大大的简化了代码的编写,但是在一些场景下我们并不希望冒泡存在,所以我可以通过时间对象来取消冒泡

event.target 触发事件的对象

event.currentTarget 绑定事件的对象(同this)

event.stopPropagetion( ) 停止事件传导

event.preventDefault( ) 取消默认行为

<!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>

        <a id="chao" href="https://parlo.cc">超链接</a>

        <script>
            // 事件对象
            const box1 = document.getElementById("box1")
            const box2 = document.getElementById("box2")
            const box3 = document.getElementById("box3")
            const chao = document.getElementById("chao")

            chao.addEventListener("click", (event) => {

                event.preventDefault() // 取消默认行为

                alert("被点了~~~")

            })

            box1.addEventListener("click", function (event) {
                // alert(event)
                // console.log(event.target)
                // console.log(this)

                console.log(event.currentTarget)

                // alert("Hello 我是box1")
            })

            // box2.addEventListener("click", function(event){
            //     event.stopPropagation()
            //     alert("我是box2")
            // })

>事件委派

委派就是将本该绑定给多个元素的事件,统一绑定给 document,这样可以降低代码复杂度,方便维护。

<!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="btn">点我一下</button>

        <hr />

        <ul id="list">
            <li><a href="javascript:;">链接一</a></li>
            <li><a href="javascript:;">链接二</a></li>
            <li><a href="javascript:;">链接三</a></li>
            <li><a href="javascript:;">链接四</a></li>
        </ul>

        <script>
            /* 
                我一个希望:
                    只绑定一次事件,既可以让所有的超链接,包括当前的和未来新建的超链接都具有这些事件

                思路:
                    可以将事件统一绑定给document,这样点击超链接时由于事件的冒泡,
                        会导致document上的点击事件被触发,这样只绑定一次,所有的超链接都会具有这些事件
            */

            const list = document.getElementById("list")
            const btn = document.getElementById("btn")

            // 获取list中的所有链接
            const links = list.getElementsByTagName("a")


            document.addEventListener("click", (event) => {
                // 在执行代码前,先来判断一下事件是由谁触发
                // 检查event.target 是否在 links 中存在

                // console.log(Array.from(links))

                if([...links].includes(event.target)){
                    alert(event.target.textContent)
                }                
            })

            // 点击按钮后,在ul中添加一个新的li
            btn.addEventListener("click", () => {
                list.insertAdjacentHTML(
                    "beforeend",
                    "<li><a href='javascript:;'>新超链接</a></li>"
                )
            })
        </script>
    </body>
</html>

>事件的捕获

在 DOM 中,事件的传播主要分为三个阶段

  • 捕获阶段(从祖先元向目标元素进行事件的捕获)

  • 目标阶段(触发事件的对象)

  • 冒泡阶段(由目标元素向祖先元素进行事件的冒泡)

当前元素触发事件后,会先从当前元素最大的祖先元素开始向当前元素进行事件的捕获。默认情况下,事件不会在捕获阶段触发。

如果希望在捕获阶段触发事件,可以将 addEventListener 的第三个参数设置为 true。一般情况下我们不希望事件在捕获阶段实现,所以通常我们并不需要设置第三个参数。

event.Phase 表示事件触发的阶段

1 捕获阶段,2 目标阶段,3 冒泡阶段

<!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)
            })

            box2.addEventListener("click", event => {

                alert("2" + event.eventPhase)
            })

            box3.addEventListener("click", event => {
                alert("3" + event.eventPhase)
            })

        </script>
    </body>
</html>