DOM,全称Document Object Model,中文翻译为文档对象模型。DOM属于Web API的一部分。Web API中定义了非常多的对象,通过这些对象可以完成对网页的各种操作(添加删除元素、发送请求、操作浏览器等)
DOM中的D意为Document,即文档。所谓文档就是指整个网页,换言之,DOM是用来操作网页的。O意为Object,即对象。DOM将网页中的每一部分内容都转换为了对象,div有div的对象,input有input的对象,甚至一段文本,一段注释也有其所对应的对象。转换对象以后,我们就可以以面向对象的方式去操作网页,想要操作哪个元素就获取哪个元素的对象,然后通过调用其方法或属性完成各种操作。M意为Model,即模型。模型用来表示对象之间的关系,也就是父子元素、祖先后代、兄弟元素等,明确关系后我们便可以通过任意一个对象去获取其他的对象
网页中的各种元素(如标签、文本、属性等)被表示为对象,这些对象以树状结构组织起来,称为 DOM 树
例如,对于一个简单的 HTML 页面
Hello, World!
,其 DOM 树的根节点是html,html节点下有body子节点,body节点下又有p子节点,p节点包含文本内容 “Hello, World!”
document对象
要使用DOM来操作网页,我浏览器至少得先给我一个对象才能去完成各种操作,浏览器已经为我们提供了一个document对象,它是一个全局变量可以直接使用
<body><button id = "btn">按钮</button><script>// 通过id获取btn对象const btn = document.getElementById("btn")// 将按钮文案更改为新按钮// innerText的用于获取或者设置元素内部的文本内容btn.innerText = "新按钮"</script></body>
- document对象表示的是整个网页
- document对象的原型链
HTMLDocument -> Document -> Node -> EventTarget -> Object.prototype -> null
- 凡是在原型链上存在的对象的属性和方法都可以通过Document去调用
- 部分属性:
document.documentElement --> html根元素
document.head --> head元素
document.title --> title元素
document.body --> body元素
document.links --> 获取页面中所有的超链接 - 用法大全
- document对象的原型链
节点
在DOM标准下,网页中的每一个部分都会转换为对象。这些对象有一个共同的称呼——节点(Node)。一个页面将会由多个节点构成,虽然都称为节点,但是它们却有着不同的类型:文档节点、元素节点、文本节点、属性节点....
每一个节点都有其不同的作用,文档节点表示整个网页,元素节点表示某个标签,文本节点表示网页中的文本内容,属性节点表示标签中的各种属性。如果从对象的结构上来讲,这些对象都有一个共同的父类Node。总的来说,都是属于节点,但是具体类型不同
元素节点
元素节点对象(element),在网页中,每一个标签都是一个元素节点
<body><button id = "btn" class="btn-class">按钮</button><input type="radio" name="one"><script>// 通过id获取元素节点对象const btnId = document.getElementById("btn")console.log(btnId) // <button id="btn" class="btn-class">按钮</button>// 通过class获取所有符合条件的元素条件对象,返回的是一个类数组对象// 该方法返回的结果是一个实时更新的集合,当网页中新添加元素时,集合也会实时更新const btnClass = document.getElementsByClassName("btn-class")console.log(btnClass) // HTMLCollection[button#btn.btn-class, btn: button#btn.btn-class]// 根据标签名获取一组元素节点对象// 该方法返回的结果是一个实时更新的集合,当网页中新添加元素时,集合也会实时更新// document.getElementsByTagName("*") 获取页面中所有的元素const btnTag = document.getElementsByTagName("button")console.log(btnTag) // HTMLCollection[button#btn.btn-class, btn: button#btn.btn-class]// 根据name属性获取一组元素节点对象, 返回一个实时更新的集合,主要用于表单const inputName = document.getElementsByName("one")console.log(inputName) // NodeList[input]// 根据各种css选择器去页面中查询元素,会返回一个类数组(不会实时更新)const query = document.querySelectorAll(".btn-class")console.log(query) // NodeList[button#btn.btn-class]// 根据选择器去页面中查询第一个符合条件的元素,没有则返回nullconst firstQuery = document.querySelector("button")console.log(firstQuery) // <button id="btn" class="btn-class">按钮</button>// 根据标签名创建一个元素节点对象,只是创建,未插入到网页const newTag = document.createElement("h1")console.log(newTag) // <h1></h1></script></body>
/*
通过元素节点对象获取其他节点
*/<body><div id = "box"><button id = "btn" class="btn-class">按钮</button><input type="radio" name="one"></div><button class="btn-class">按钮2</button><script>const element = document.getElementById("box")// document对象的方法元素节点对象可以同样使用,区别是在于获取的是当前对象内的指定对象// 获取element内的button对象const btn = element.getElementsByTagName("button")console.log(btn) // HTMLCollection[button#btn.btn-class, btn: button#btn.btn-class]// 获取当前元素的子节点(包含空白的子节点)const childNodes = element.childNodesconsole.log(childNodes) // NodeList(5) [text, button#btn.btn-class, text, input, text]// 获取当前元素的子元素const children = element.childrenconsole.log(children) // HTMLCollection(2)[button#btn.btn-class, input, btn: button#btn.btn-class, one: input]// 获取当前元素的第一个子元素const firstElementChild = element.firstElementChildconsole.log(firstElementChild) // <button id = "btn" class="btn-class">按钮</button>// 获取当前元素的最后一个子元素const lastElementChild = element.lastElementChildconsole.log(lastElementChild) // <input type="radio" name="one">// 获取当前元素的下一个兄弟元素const nextElementSibling = element.nextElementSiblingconsole.log(nextElementSibling) // <button class="btn-class">按钮2</button>// 获取当前元素的前一个兄弟元素const previousElementSibling = element.previousElementSiblingconsole.log(previousElementSibling) // null// 获取当前元素的父节点const parentNode = element.parentNodeconsole.log(parentNode) // <body>...</body>// 获取当前元素的标签名const tagName = element.tagNameconsole.log(tagName) // DIV</script></body>
文本节点
在DOM中,网页中所有的文本内容都是文本节点对象,包括空文本、换行和空格
<body><div id = "box">asdasd<button id = "btn" class="btn-class">按钮</button><input type="radio" name="one"></div><button class="btn-class">按钮2</button><script>// 可以通过元素来获取其中的文本节点对象,但是我们通常不会这么做,我们可以直接通过元素去修改其中的文本const element = document.getElementById("box")const t = element.firstChildconsole.log(t) // asdasd</script></body>
直接通过元素去修改其中的文本
<body><div id = "box">asdasd<button id = "btn" class="btn-class">按钮</button><input type="radio" name="one"></div><button class="btn-class">按钮2</button><script>const element = document.getElementById("box")// textContent获取或修改元素中的文本内容,获取的是标签中的内容,不会考虑css样式// 当字符串中有标签时,会自动对标签进行转义显示在页面,不会渲染// 如果修改会在网页直接生效console.log(element.textContent) // 获取文本:asdasd 按钮console.log(element.firstChild.textContent) // 获取element第一个子节点文本:asdasdelement.firstChild.textContent = "new t" // 将第一个文本修改为new t// innerText获取或修改元素中的文本内容,会考虑css样式,会触发网页的重排(计算CSS样式)// 当字符串中有标签时,会自动对标签进行转义显示在页面,不会渲染// 如果修改会在网页直接生效// 获取element的第一个元素子节点文本console.log(element.firstElementChild.innerText) // 按钮element.firstElementChild.innerText = "大按钮" // 修改element的第一个元素子节点文本// innerHTML获取或修改元素中的html代码,可以直接向元素中添加html代码,innerHTML插入内容时,有被xss注入的风险// 当字符串中有标签时,会在网页生效console.log(element.firstElementChild.innerHTML) // 大按钮'element.firstElementChild.innerHTML = "<h1>新大按钮</h1>" // h1效果会在网页生效</script></body>
属性节点
属性节点(Attr)在DOM也是一个对象,通常不需要获取对象而是直接通过元素即可完成对其的各种操作
<input class="admin" type="text" name="username" value="user"><input class="admin1" type="text" name="username1" value="user1"><script>const input = document.querySelector("[name=username]")const input2 = document.querySelector(".admin1")// 通过元素.属性名 可以读取、修改对应的数据console.log(input.className) // admin,读取class属性比较特殊,需要使用classNameconsole.log(input.type) // textconsole.log(input.name) // usernameinput.value = "new user" // 修改value的值为new userconsole.log(input.disabled) // false 读取一个布尔值时,会返回true或false// 通过方法读取、修改对象的属性// 读取属性console.log(input2.getAttribute("type")) // text// 修改属性,将value属性的值修改为uuuinput2.setAttribute("value","uuu")// 删除属性input2.removeAttribute("value")</script></body>
事件
事件(event)
- 事件就是用户和页面之间发生的交互行为
比如:点击按钮、鼠标移动、双击按钮、敲击键盘、松开按键...- 可以通过为事件绑定响应函数(回调函数),来完成和用户之间的交互
- 绑定响应函数的方式:
1.可以直接在元素的属性中设置
2.可以通过为元素的指定属性设置回调函数的形式来绑定事件(一个事件只能绑定一个响应函数)
3.可以通过元素addEventListener()方法来绑定事件
<body><button id="btn">点击</button><script>// 获取按钮对象const btn = document.getElementById("btn")// 为该按钮的点击事件设置响应函数,该方式一个事件只能绑定一个响应函数,如果后续重复绑定会被覆盖btn.onclick = function (){alert("onclick!!!")}// 通过addEventListener绑定事件 第一个参数是触发的事件,第二个参数是响应函数// 该方式一个事件可以绑定多个响应函数,根据绑定顺序执行btn.addEventListener("click",function (){console.log("addEventListener")})btn.addEventListener("click",function (){alert("addEventListener")})
</script></body>
文档的加载
网页是自上向下加载的,如果将js代码编写到网页的上边,js代码在执行时,网页还没有加载完毕,这时会出现无法获取到DOM对象的情况
如何解决这个问题:
1. 将script标签编写到body的最后 2. 将代码编写到window.onload的回调函数中 3. 将代码编写到document对象的DOMContentLoaded的回调函数中(执行时机更早) 4. 将代码编写到外部的js文件中,然后以defer的形式进行引入(执行时机更早,早于DOMContentLoaded)
<script>// window.onload 事件会在窗口中的所有内容(一个网页可能有外嵌页面)加载完毕之后才触发// 窗口内容加载完之后触发响应函数执行js代码window.onload = function () {// 获取按钮对象const btn = document.getElementById("btn")btn.addEventListener("click", function () {alert("addEventListener")})}</script><body><button id="btn">点击</button></body>
// DOMContentLoaded事件会在当前文档加载完后执行document.addEventListener("DOMContentLoaded",function (){// 获取按钮对象const btn = document.getElementById("btn")btn.addEventListener("click", function () {alert("addEventListener")})})
<!-- 引入外部js文件 -->
<!-- 如果默认引入在body上,依然会有加载时机问题 -->
<!-- defer 关键字会延迟加载,当前文档加载完再加载,解决该问题--><script defer src = "./script/index.js"></script>
DOM元素的修改
创建元素
appendChild
appendChild()是 DOM节点对象的一个方法。它的主要作用是将一个节点添加到另一个节点的子节点列表的末尾。这是一种在 HTML 文档结构中动态添加元素的方式
<body><button id="btn">按钮</button><ul id="list"><li id="one">1</li><li id="two">2</li><li id="three">3</li>
</ul></body><script>// 获取ulconst list = document.getElementById("list")// 获取按钮const btn = document.getElementById("btn")// 点击按钮的时候触发btn.onclick = function (){// 创建一个liconst li = document.createElement("li")// li的文本li.textContent = "4"// 设置li的idli.id = "four"// 把创建的li元素添加到ul里面// appendChild把一个节点添加到另一个节点list.appendChild(li)}</script>
insertAdjacentElement
insertAdjacentElement()是js的一个 DOM 操作方法。它用于将一个指定的元素插入到另一个元素的附近特定位置
// 点击按钮的时候触发btn.onclick = function (){// 创建一个liconst li = document.createElement("li")// li的文本li.textContent = "4"// 设置li的idli.id = "four"// 把创建的li元素添加到ul里面// 参数:1. 要添加的位置 2. 要添加的元素list.insertAdjacentElement("afterbegin",li)// beforeend 标签的最后 afterbegin 标签的开始// beforebegin 在元素的前边插入元素(兄弟元素) afterend 在元素的后边插入元素(兄弟元素)}
insertAdjacentHTML
insertAdjacentHTML()用于在指定元素的附近插入 HTML 字符串的方法。允许你将 HTML 代码片段作为字符串插入到元素的不同位置,有XSS攻击的风险
// 点击按钮的时候触发btn.onclick = function (){// 参数:1. 要添加的位置 2. 要插入的html代码字符串list.insertAdjacentHTML("afterbegin","<li id='5'>5</li>>")// beforeend 标签的最后 afterbegin 标签的开始// beforebegin 在元素的前边插入元素(兄弟元素) afterend 在元素的后边插入元素(兄弟元素)}
替换元素
// 获取 id=one的liconst one = document.getElementById("one")// 创建一个新的liconst newOne = document.createElement("li")newOne.textContent = "1111"newOne.id="new-one"// replaceWith 使用一个元素替换当前元素// newOne替换oneone.replaceWith(newOne)
删除元素
const two = document.getElementById("two")// remove()方法用来删除当前元素two.remove()
取消元素的默认行为
<!--javascript:; 执行代码 但是代码是空的,即不发生任何行为操作-->
<a href="javascript:;"></a>
使用return false来取消默认行为,只在 xxx.xxx = function(){}这种形式绑定的事件中才适用
// 响应函数function delUserInfo(){// 为所有超链接绑定了该响应函数,点击哪个链接,this就是哪个// parentNode是一个节点属性。它用于获取当前节点的父节点const tr = this.parentNode.parentNode// 获取用户昵称const userName = tr.firstElementChild.textContent// confirm()是一个浏览器提供的全局函数,用于显示一个带有 “确定” 和 “取消” 按钮的模态对话框。这个对话框用于向用户询问一个是 / 否的问题,并且等待用户做出响应if (confirm(`确认要删除${userName}吗`)){tr.remove()}// 取消默认事件return false}// 获取所有超链接const links = document.links// 为所有的链接绑定单级响应函数for (let link of links){link.onclick = delUserInfo}
节点的复制
// 获取ul列表const list = document.getElementById("list")// 获取id为li1的元素节点const li1 = document.getElementById("li1")// 使用li1节点复制一份,赋值给newLiconst newLi = li1.cloneNode(true)newLi.id = "li2"list.appendChild(newLi)/* 使用 cloneNode() 方法对节点进行复制时,它会复制节点的所有特点包括各种属性这个方法默认只会复制当前节点,而不会复制节点的子节点可以传递一个true作为参数,这样该方法也会将元素的子节点一起复制*/
通过DOM操作css
使用style读取、修改内联样式
// 获取box元素const box = document.getElementById("box")// 修改box的内联样式样式box.style.width = "400px"box.style.height = "400px"// 如果样式名中含有-,则需要将样式表修改为驼峰命名法,background-color --> backgroundColorbox.style.backgroundColor = "yellow" // 读取内联样式console.log(box.style.height)
读取当前生效的样式
// 获取box元素const box = document.getElementById("box")// getComputedStyle返回一个只读对象,这个对象中包含了当前元素所有的生效的样式,无法通过该方式修改样式// 参数:要获取样式的对象,要获取的伪元素const styleObj = box.getComputedStyle(box)// 当前生效的宽度console.log(styleObj.width)// 当前生效的高度console.log(styleObj.height)// 获取before伪元素的样式const styleObj1 = box.getComputedStyle(box,"::before")console.log(styleObj1.width)
其他读取样式的方式
// 获取box元素const box = document.getElementById("box")// 获取元素内部的宽度和高度,包含内容区和内边距console.log(box.clientWidth)console.log(box.clientHeight)// 获取元素的可见框的大小,包含内容区、内边距和边框console.log(box.offsetWidth)console.log(box.offsetHeight)// 获取元素滚动区域的宽度和高度console.log(box.scrollWidth)console.log(box.scrollHeight)// 获取元素的定位父元素,离当前元素最近的开启了定位的祖先元素,如果所有的元素都没有开启定位则返回bodyconsole.log(box.offsetParent)// 获取元素相对于其定位父元素的偏移量console.log(box.offsetTop)console.log(box.offsetLeft)//获取 or 设置元素滚动条的偏移量console.log(box.scrollTop)console.log(box.scrollLeft)box.scrollTop = 100 // 设置偏移量
操作class修改css样式
除了直接修改样式外,也可以通过修改class属性来间接的修改样式
<style>.btn{width: 200px;}</style><script>const btn1 = document.getElementById("btn1")const btn2 = document.getElementById("btn2")// 在原先class名字的基础上 加一个btn的class// css样式中的类选择器命中设置样式btn1.className += " btn"btn2.className += " btn"</script>
通过class修改样式的好处:
1. 可以一次性修改多个样式2. 对JS和CSS进行解耦
const btn1 = document.getElementById("btn1")// 元素.classList 是一个对象,对象中提供了对当前元素的类的各种操作方法// 向元素中添加一个或者多个classbtn1.classList.add("btn2","btn3")// 移除元素中的一个或多个classbtn1.classList.remove("btn2")// 切换元素中的class,如果有btn2的class,则删除,如果没有,就加上btn1.classList.toggle("btn2")// 替换class,将btn1替换为btn2btn1.classList.replace("btn1","btn2")// 检查class,有该class返回true,否则falsebtn1.classList.contains("btn1")
事件对象
事件对象:
事件对象是有浏览器在事件触发时所创建的对象,这个对象中封装了事件相关的各种信息
通过事件对象可以获取到事件的详细信息,比如:鼠标的坐标、键盘的按键..
浏览器在创建事件对象后,会将事件对象作为响应函数的参数传递,
所以我们可以在事件的回调函数中定义一个形参来接收事件对象
const box = document.getElementById("box")// 监听事件,在创建事件对象后,会将事件对象作为响应函数的参数传递,在事件的回调函数中定义一个形参来接收事件对象box.addEventListener("mousemove",event => {console.log(event) // 包含很多属性和方法的对象,具体取决于触发的事件类型console.log(event.clientX) // 获取X轴console.log(event.clientY) // 获取Y轴})box.addEventListener("click",function (event){console.log(event)})
Event对象
在DOM中存在着多种不同类型的事件对象,多种事件对象有一个共同的祖先 Event
const box = document.getElementById("box")box.addEventListener("click", event => {// 触发事件的对象console.log(event.target) // id 为box的元素// 绑定事件的对象(与事件冒泡有关),同thisconsole.log(event.currentTarget)/*- 事件的冒泡就是指事件的向上传导- 当元素上的某个事件被触发后,其祖先元素上的相同事件也会同时被触发,由里向外- 冒泡的存在大大的简化了代码的编写,但是在一些场景下我们并不希望冒泡存在,可以通过事件对象来取消冒泡- 事件的冒泡和元素的样式无关,和结构相关*/// 取消事件传导(冒泡)event.stopPropagation()// 取消默认行为event.preventDefault()})
事件委派
只绑定一次事件,即可以让所有的元素对象,包括当前和未来新建的都具有这些事件
委派就是将本该绑定给多个元素的事件,统一绑定给document然后根据判断逻辑由什么元素触发,这样可以降低代码复杂度方便维护
<body>
<button id="btn">按钮</button><ul id="list"><li><a href="#">1</a></li><li><a href="#">2</a></li><li><a href="#">3</a></li>
</ul></body><script>const list = document.getElementById("list")const btn = document.getElementById("btn")// 获取list中所有超链接const links = list.getElementsByTagName("a")// 可以将事件统一绑定给document,这样点击事件的冒泡,会导致document上的点击事件被触发// 这样只绑定一次,所有的点击都会具有这些事件document.addEventListener("click", (event) => {// 判断事件是由谁触发,event.target 是否在 links 中存在// 只有list中的超链接点击生效,如果不判断事件触发,页面所有点击效果都会生效if ([...links].includes(event.target)) {alert(event.target.textContent)}})// 点击按钮后,在ul中加一个新的libtn.addEventListener("click", () => {list.insertAdjacentHTML("beforeend", "<li><a href='#'>new</a></li>")})</script>
事件捕获
在DOM中,事件的传播可以分为三个阶段:
1.捕获阶段 (由祖先元素向目标元素进行事件的捕获)(默认情况下,事件不会在捕获阶段触发)
2.目标阶段 (触发事件的对象)
3.冒泡阶段 (由目标元素向祖先元素进行事件的冒泡)
事件的捕获,指事件从外向内的传导:
- 当前元素触发事件以后,会先从当前元素最大的祖先元素开始向当前元素进行事件的捕获
- 冒泡是由里向外,而捕获是是由外向里
const box = document.getElementById("box")box.addEventListener("click", event => {// eventPhase 表示事件触发的阶段console.log(event.eventPhase)// 如果希望在捕获阶段触发事件,可以将addEventListener的第三个参数设置为true}, true)