DOM – 李立超 | lilichao.com
前面在学习JS的时候发现,似乎JS和网页并没有太大的关系。换句话说,我们所编写的JS代码,除了是写在网页中以外,并没有和网页产生任何实质的联系。
而DOM就是一种使用JS来操作网页的技术
DOM,全称Document Object Model,中文翻译为文档对象模型。
DOM属于Web API的一部分。Web API中定义了非常多的对象,通过这些对象可以完成对网页的各种操作(添加删除元素、发送请求、操作浏览器等)
My Title
A Heading
Link Text
对于上面的代码,我们可以得到如下DOM树
在DOM标准下,网页中的每一个部分都会转换为对象。这些对象有一个共同的称呼——节点(Node)。
一个页面将会由多个节点构成,虽然都称为节点,但是它们却有着不同的类型:
每一个节点都有其不同的作用,文档节点表示整个网页,元素节点表示某个标签,文本节点表示网页中的文本内容,属性节点表示标签中的各种属性。如果从对象的结构上来讲,这些对象都有一个共同的父类Node。总的来说,都是属于节点,但是具体类型不同。
要使用DOM来操作网页,我们需要浏览器至少得先给我一个对象,才能去完成各种操作
所以浏览器已经为我们提供了一个document对象,它是一个全局变量可以直接使用document
代表的是整个的网页
Title
HTMLDocument -> Document -> Node -> EventTarget -> Object.prototype -> null
document.documentElement
--> html
根元素document.head
--> head
元素document.title
--> title
元素document.body
--> body
元素document.links
--> 获取页面中所有的超链接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; ialert(spans[i])spans[i].innerText = "哈哈哈"+i
}
document.getElementById()
根据id获取一个元素节点对象
document.getElementsByClassName()
document.getElementsByTagName()
document.getElementsByTagName()
获取页面中所有的元素
document.getElementsByName()
document.querySelectorAll()
document.querySelector()
根据选择器去页面中查询第一个符合条件的元素
document.createElement()
创建一个元素节点
根据标签名创建一个元素节点对象
div元素的原型链
通过元素节点对象获取其他节点的方法
element.childNodes
:获取当前元素的子节点(会包含空白的子节点)element.children
:获取当前元素的子元素,不包含文本节点,使用更多一些element.firstElementChild
:获取当前元素的第一个子元素element.lastElementChild
:获取当前元素的最后一个子元素element.nextElementSibling
:获取当前元素的下一个兄弟元素element.previousElementSibling
:获取当前元素的前一个兄弟元素element.parentNode
:获取当前元素的父节点element.tagName
:获取当前元素的标签名在DOM中,网页中所有的文本内容都是文本节点对象,可以通过元素来获取其中的文本节点对象,但是我们通常不会这么做
我们可以直接通过元素去修改其中的文本,修改文本的三个属性
element.textContent
element.innerText
display: none
通过innerText就获取不到文本,通过textContent就可以获取到文本
--> <li>
,所以在网页上还是会原样显示出来添加的文本element.innerHTML
方式一:
className
来读取),读取一个布尔值时,会返回true或false方式二:
元素.getAttribute(属性名)
元素.setAttribute(属性名, 属性值)
元素.removeAttribute(属性名)
Document
事件(event)
事件就是用户和页面之间发生的交互行为
可以通过为事件绑定响应函数(回调函数),来完成和用户之间的交互
绑定响应函数的方式:
可以直接在元素的属性中设置
可以通过为元素的指定属性设置回调函数的形式来绑定事件(一个事件只能绑定一个响应函数)
可以通过元素addEventListener()
方法来绑定事件
网页是自上向下加载的,如果将js代码编写到网页的上边,js代码在执行时,网页还没有加载完毕,这时会出现无法获取到DOM对象的情况
Title
上述代码在打印的时候就会打印出来null
,表示无法获取到button元素
如果将script移动到button之后,就可以正常打印出结果
Title
为了保证在任意位置写的代码均生效我们可以使用下面两个函数
例如引入iframe之后,window.onload会在iframe全部加载完成之后才会执行,document会在当前文档加载完成之后执行,而不会等待另一个iframe加载完成之后才执行
Title
如何解决这个问题:
将script标签编写到body的最后
将代码编写到window.onload的回调函数中
将代码编写到document对象的DOMContentLoaded的回调函数中(执行时机更早)
将代码编写到外部的js文件中,然后以defer的形式进行引入(执行时机更早,早于DOMContentLoaded)
实现点击切换图片,并实现循环切换
Title
Title
document.createElement(tagName)
:创建一个标签document.appendChild()
:用于给一个节点添加子节点document.insertAdjacentElement()
:可以向元素的任意位置添加元素 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", "唐僧 ")
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()
实现表格的增删
注意事项
a标签的跳转
xxx.xxx = function(){}
这种方式绑定的事件中才适用javascript:;
改为执行这段代码,也可以取消默认跳转form中的button中type设置为button可以取消默认的提交事件
这种写法,容易被xss的攻击,当用户在姓名地方写入,然后我们将这个数据存入到数据库中,在下个用户加载页面的时候就会自动执行这个脚本,风险很大,所以,当要渲染用户自己输入的内容的话,使用
xxx.innerText = xx
修改,这种方法会自动转移其中的字符
const tbody = document.querySelector("tbody")tbody.insertAdjacentHTML("beforeend",`${name} ${email} ${salary} 删除
`
)
Title
document.cloneNode()
:对节点进行复制时,它会复制节点的所有特点包括各种属性 下面案例演示将一个li节点从一个list复制到另一个list中
如果直接使用appendChild的话则id为l1的标签会在第一个ul中消失
Document - 孙悟空
- 猪八戒
- 沙和尚
- 蜘蛛精
元素.style.样式名 = 样式值
修改element的样式 -
,则需要将样式表修改为驼峰命名法案例:点击按钮后,修改box1的宽度
Document
元素.style.样式名
读取到的是内联样式,如果是通过class定义的样式,则这种形式不能获取到样式
正确的方式是使用getComputedStyle()
来读取样式
它会返回一个对象,这个对象中包含了当前元素所有的生效的样式
参数:
返回值:返回的一个对象,对象中存储了当前元素的样式
注意:样式对象中返回的样式值,不一定能来拿来直接计算,所以使用时,一定要确保值是可以计算的才去计算,如果获取数值的话可以使用parseInt()
处理,在处理完之后设置的时候记得再加回去
案例:点击按钮后,获取box的样式
Document
Document
如果直接通过js代码中修改某个样式,会造成代码耦合太高,我们可以通过修改class属性来间接的修改样式
通过class修改样式的好处:
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的宽度
Document
案例:获取鼠标的坐标
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
})
Event - Web API 接口参考
案例:阻止事件冒泡
Document
案例:取消超链接的默认跳转行为
const link = document.querySelector("a")link.addEventListener("click", (event) => {event.preventDefault() // 取消默认行为alert("被点了~~~")})
event.stopPropagation()
案例:图标跟随鼠标
Title
案例:在一片区域中图标不跟随鼠标(使用阻止冒泡事件实现效果)
Title
委派就是将本该绑定给多个元素的事件,统一绑定给父级元素,这样可以降低代码复杂度方便维护
案例:点击li标签,打印出其中的内容
思路:原来是通过在每一个li上都绑定一个点击事件,然后对新加的li也再添加事件,但是现在可以使用事件的委派来实现这个操作,也就是直接绑定在父级元素上
Title
- 链接1
- 链接2
- 链接3
- 链接4
事件的传播机制
通过
event.eventPhase
可以获得事件触发的阶段1 捕获阶段 2 目标阶段 3 冒泡阶段
stopPropagation()
可以停止捕获或者冒泡,在执行到的函数停止
Document
通过定时器,可以使代码在指定时间后执行
setTimeout()
clearTimeout(标识)
const timer = setTimeout(()=>{alert("我是定时器中的代码")
}, 3000)clearTimeout(timer)
setInterval()
(每间隔一段时间代码就会执行一次)
clearInterval()
let num = 0const timer = setInterval(() => {num++numH1.textContent = numif(num === 200){clearInterval(timer)}
}, 30)
定时器的原理是在到时之后将定时器的回调函数放到消息队列中,等调用栈中的函数执行完成之后才去执行
通过以下代码就可以看到,定时器并不是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的回调函数的最后,在调用一个setTimeoutsetTimeout(fn, 3000)
}, 3000)
事件循环(event loop)
对于下列代码,执行到fn2()函数内部时调用栈如图,当执行玩fn2()之后会将fn2对应栈帧从调用栈中弹出,直到执行完所有代码
当我们点击页面中的按钮的时候,对应的点击事件会先加入到消息队列中,等待调用栈中的所有代码全部执行完了之后,再从消息队列中取出第一个事件到调用栈中执行,直到消息队列为空,然后后面会定期扫描消息队列,如果消息队列中有新消息,则会继续执行
调用栈先于消息队列执行的证据:
下面这段代码,setTimeout()
0ms后就应该执行,说明就是立即执行的,但是实际运行结果却是2222先打印
setTimeout(() => {console.log(11111)
}, 0)console.log(222222)