[TOC]
DOM(Document Object Model),文档对象模型,属于Web API的一部分,其中定义了很多的对象,用来对网页进行操作。
在DOM标准下,网页的每部分都会转化为对象,称之为节点。一个页面由多个节点构成,节点有着不同的类型:
文档节点 --- 整个网页
元素节点 --- 某个元素
文本节点 --- 网页中的文本
属性节点 --- 元素的某属性
...
元素之间的关系:
祖先 --- 包含后代元素的元素
后代 --- 被祖先元素包含的元素
父 --- 直接包含子元素的元素
子 --- 被父元素直接包含的元素
兄弟 --- 拥有相同父元素的元素
使用JS操作网页时,第一步需要先获取对象,然后在进行操作。浏览器已经提供了Document对象。
const 对象名 = document.getElementById("id名"); --- 通过id获取元素节点对象
对象名.innerText = "需要修改的文本"; --- 修改元素的文本内容
<body>
<button id="btn">点我一下</button>
<script>
const btn = document.getElementById("btn");
btn.innerText = "Hello";
</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对象获取已有元素节点:
document.getElementById(); --- 通过id获取元素节点对象。
document.getElementsByClassName(); --- 根据元素的class属性值获取一组元素节点对象,返回的是一个类数组。
document.getElementsByTagtName(); --- 通过标签名获取一组元素节点对象,返回结果是实时更新的集合,当参数是“*”时,代表获取页面内所有元素。
document.getElementByName(); --- 根据name属性值来获取元素对象节点,主要运用于表单,返回实时更新的集合。
document.querySelectorAll(); --- 根据选择器来获取元素节点对象,如(div,.name,#btn),返回的是不会实时更新的类数组。
document.querySelector(); --- 根据选择器查询第一个符合条件的元素。
通过元素节点对象获取其他节点的方法:
element.childNodes --- 获取当前元素的子节点
element.children --- 获取当前元素的子元素
element.firstElementChild --- 获取当前元素的第一个子元素
element.firstElementChild --- 获取当前元素的最后一个子元素
element.nextElementSibling --- 获取当前元素的下一个兄弟元素
element.perviousElementSibling --- 获取当前元素的前一个兄弟元素
element.parentNode --- 获取当前元素的父节点
element.tagName --- 获取当前元素的标签名
文本节点对象:页面中所有文本内容都是文本节点对象。修改文本的三个属性:
element.textContent
获取或修改元素中的文本内容。
获取的是标签中的内容,不会考虑CSS样式。
element.innerText
获取或修改元素中的内容。
会考虑CSS样式,通过该属性读取CSS样式,会触发网页的重排。
当修改的文本内容中有标签时,会自动将其转义。
element.innerHTML
获取或修改元素中的html代码。
可以直接向元素中插入html代码,但是插入内容时有被xss注入的风险。
属性节点,也是DOM的一个对象,通常不需要获取对象而是直接通过元素即可对其进行各种操作。操作属性节点:
方式一:
读取:元素.属性名(class属性要通过className来读取),读取一个布尔值时返回true或false。
修改:元素.属性名 = 属性值;
方式二:
读取:元素.getAttribute(属性名);
修改:元素.setAttribute(属性名,属性值);
删除:元素.removeAttribute(属性名);
<body>
<input type="text" name="username" value="admin">
<script>
// const input = document.getElementsByName("username")[0];
const input = document.querySelector("[name = username]");
console.log(input.type);
input.value = "用户名";
console.log(input.getAttribute("name"));
input.setAttribute("name", "tim");
input.removeAttribute("value");
console.log(input);
</script>
</body>
事件: 事件就是用户和浏览器之间发生交互行为。
可以通过为事件绑定响应函数(回调函数)来完成与用户之间的交互。
绑定响应函数的方式:
直接在元素属性中设置。
通过为元素的指定属性设置响应函数来绑定事件。(一个事件只能绑定一个响应函数)
通过元素的addEventListener()方法来绑定事件。(可以绑定多个事件,依次触发,不会覆盖)
<body>
<button id="btn">按钮</button>
<script>
const btn = document.getElementById("btn");
btn.onclick = function(){
// alert("点击事件");
// };
btn.addEventListener("click", () => {
alert("点击事件1");
});
btn.addEventListener("click", () => {
alert("点击事件2");
});
</script>
</body>
解决页面加载前JS代码先执行导致的无法获取DOM对象的情况的方法:
将script标签写在body的最后。
将代码写在window.onload回调函数中。
window.onload = function(){ JS代码 };
window.addEventListener("load",function(){ JS代码 });
将代码编写到Document对象的DOMContentLoaded的回调函数中。
document.addEVentListener("DOMContentLoaded",function(){ JS代码 });
将代码编写在外部JS文件中,然后以defer的形式引入。
<script defer src="xxx"> JS代码 </script>
//练习一:点击切换图片
<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>
.outer {
width: 400px;
margin: 60px auto;
text-align: center;
}
#pervious {
margin-top: 15px;
margin-right: 25px;
}
</style>
</head>
<body>
<div class="outer">
<p id="info">总共m张图片,这是第n张</p>
<div class="img-wrapper">
<img src="./1.webp" alt="按钮图片">
</div>
<button id="pervious">前一张</button>
<button id="next">后一张</button>
</div>
<script>
const img = document.getElementsByTagName("img")[0];
const info = document.getElementById("info");
const perv = document.getElementById("pervious");
const next = document.getElementById("next");
const imgArr = ["./1.webp", "./2.webp", "./3.webp"];
let count = 0;
info.textContent = `总共${imgArr.length}张,这是第${count + 1}张`;
perv.onclick = function () {
count--;
if (count < 0) {
count = imgArr.length - 1;
};
img.src = imgArr[count];
info.textContent = `总共${imgArr.length}张,这是第${count + 1}张`;
};
next.onclick = function () {
count++;
if (count > imgArr.length - 1) {
count = 0;
};
img.src = imgArr[count];
info.textContent = `总共${imgArr.length}张,这是第${count + 1}张`;
};
</script>
</body>
//练习二:学生兴趣选择
<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>
#choose,
#items,
#button {
margin-top: 20px;
}
#all,
#no,
#reverse,
#submit {
margin-right: 15px;
}
</style>
<script>
window.onload = function () {
const allBtn = document.getElementById("all");
const noBtn = document.getElementById("no");
const reverse = document.getElementById("reverse");
const submit = document.getElementById("submit");
const hobbies = document.getElementsByName("hobby");
const checkAll = document.getElementById("check-all");
allBtn.onclick = function () {
for (let i = 0; i < hobbies.length; i++) {
hobbies[i].checked = true;
};
checkAll.checked = true;
};
noBtn.onclick = function () {
for (let i = 0; i < hobbies.length; i++) {
hobbies[i].checked = false;
};
checkAll.checked = false;
};
reverse.onclick = function () {
for (let i = 0; i < hobbies.length; i++) {
hobbies[i].checked = !hobbies[i].checked;
};
const checkedBox = document.querySelectorAll('[name = hobby]:checked');
if (checkedBox.length === hobbies.length) {
checkAll.checked = true;
} else {
checkAll.checked = false;
};
};
submit.onclick = function () {
for (let i = 0; i < hobbies.length; i++) {
if (hobbies[i].checked) {
alert(hobbies[i].value);
};
};
};
checkAll.onchange = function () {
for (let i = 0; i < hobbies.length; i++) {
hobbies[i].checked = checkAll.checked;
};
};
for (let i = 0; i < hobbies.length; i++) {
hobbies[i].onchange = function () {
const checkedBox = document.querySelectorAll('[name = hobby]:checked');
if (checkedBox.length === hobbies.length) {
checkAll.checked = true;
} else {
checkAll.checked = false;
};
};
};
};
</script>
</head>
<body>
<form action="#">
<div id="choose">
请选择你喜欢的运动
<input type="checkbox" id="check-all">全选
</div>
<div id="items">
<input type="checkbox" name="hobby" value="篮球">篮球
<input type="checkbox" name="hobby" value="足球">足球
<input type="checkbox" name="hobby" value="羽毛球">羽毛球
<input type="checkBox" name="hobby" value="乒乓球">乒乓球
<input type="checkbox" name="hobby" value="跑步">跑步
</div>
<div id="button">
<button type="button" id="all">全选</button>
<button type="button" id="no">取消</button>
<button type="button" id="reverse">反选</button>
<button type="button" id="submit">提交</button>
</div>
</form>
</body>
元素的添加、删除和修改:
添加:
createElement(); --- 创建元素
元素.textContent --- 添加文本内容
元素.id --- 添加id属性
添加节点
appendChild(); --- 用于给一个节点添加子节点
insertAdjacentElement(); --- 添加元素,两个参数:参数一:添加元素的位置;参数二:添加的元素
insertAdjacentHTML(); --- 添加标签,参数一:添加的位置;参数二:添加的标签 //这种添加方法容易有xss注入风险
替换:
元素.replaceWith(替换元素); --- 用一个元素替换另一个元素
删除:
元素.remove(); --- 删除元素 如果要阻止超链接的默认跳转行为,可以在事件最后加 return false; 但是只针对 xxx.xxx = function(){}; 方式添加事件时。
节点复制: 通过 cloneNode() 方法可以实现对节点的复制,包括节点的属性,同时可以将 true 作为参数传递来判断是否复制子节点。
const l1 = document.getElementById("l1");
const newLi = l1.cloneNode(true);
修改样式: 通过 元素.style.样式名 = 样式值; 来修改元素的样式,修改添加的是内联样式,当样式名中有 - 时,需要用驼峰命名法。
获取CSS样式: 通过 getComputedStyle(); 方法,它会返回该元素所有生效的样式,第一个参数为要获取的对象,第二个参数为要获取的伪元素。(该方法是只读的)
const boxStyle = getComputedStyle(box);
console.log(boxStyle.width);
const boxBeforeStyle = getComputedStyle(box,"::before");
console.log(boxBeforeStyle.backgroundColor);
注意:样式对象中返回的样式值不一定是可以用来计算的值,所以在计算前要加以区分,确保是可计算的值再去加以计算。
const newWidth = parseInt(boxStyle.width) + 100 + "px";
通过属性读取样式:
元素.clientWidth
元素.clientHeight
获取元素内部的宽度和高度,包括内容区和内边距。
元素.offsetWidth
元素.offsetHeight
获取元素可见框的大小,包括内容区,内边距,和边框。
元素.scrollWidth
元素.scrollHeight
获取元素滚动区域的大小
元素.offsetParent
获取离元素最近的开启定位的祖先元素,如果没有找到便返回body
元素.offsetTop
元素.offsetLeft
获取元素相对于定位父元素的上边和左边的偏移量
元素.scrollTop
元素.scrollLeft
获取或设置滚动条相对于顶部和左侧的距离
通过属性读取的样式返回的都是可以直接计算的数值,也就是没有 px 后缀。
通过class属性修改样式: 除了直接修改样式外,也可以通过class属性来间接修改样式。
通过class修改样式的好处:
可以一次性修改多个元素的样式。
可以与CSS解耦。
元素.classList 是一个对象,对象中提供了当前元素的类的各种操作方法。
元素.classList.add(); --- 向元素中添加一个或多个class
元素.classList.remove(); --- 删除元素中一个或多个class
元素.classList.replace(); --- 替换元素的class,第一个参数被替换class,第二个参数为新class
元素.classList.toggle(); --- 切换元素中的一个class,有则删,没有则添加
元素.classList.contains(); --- 检查元素中是否有该class属性,有则返回true,无则返回false
事件对象(Event): 事件对象是浏览器在事件触发时创建的对象,其中包含了事件的所有信息。
通过事件对象可以获取到事件的详细信息,如鼠标的位置、偏移量,键盘的输入。
浏览器在创建事件对象后会将事件对象作为响应函数的参数传递,所以可以在事件的响应函数中定义一个形参来接受事件对象。
DOM中存在着不同类型的事件对象,所有事件对象都有一个共同的祖先:Event。
event.target(); --- 触发事件的对象
event.currentTarget(); --- 绑定事件的对象(同this)
event.stopPropagation(); --- 停止事件的传导,也就是停止该事件对象的冒泡
event.preventDefault(); --- 取消默认行为,如超链接的跳转,可以在箭头函数作为响应函数的事件中使用
事件的冒泡是指事件的向上传导,当事件触发时,其祖先元素上的相同事件也会依次触发,事件的冒泡与样式无关,只与结构有关,事件的冒泡并非坏事。
事件的委派: 将本该绑给多个元素的事件,统一绑给document对象。以降低代码复杂度方便维护。
事件的传播机制: 在DOM中,事件的传播可以分为三个阶段:
捕获阶段:由祖先元素向目标元素进行事件的捕获(默认情况下事件不会在捕获阶段触发,如若有此需求,只需将addEventListener()中的第三个参数设置为true即可)
目标阶段:触发事件的对象
冒泡阶段:由目标元素向祖先元素进行事件的冒泡
BOM: 浏览器对象模型,BOM为我们提供了一组对象,可以对浏览器进行操作。
BOM对象:
Window --- 代表浏览器窗口(全局对象)
Navigator --- 浏览器的对象,可以用来识别浏览器
navigator.userAgent --- 返回一个用来描述浏览器信息的字符串
...
Location --- 浏览器的地址栏信息
可以直接将location的值修改为新的网址,使网页发生跳转
location.href --- 获取当前网页的网址
location.assign() --- 跳转到新的地址
location.replace() --- 用新地址替代当前地址,发生跳转后无法后退
location.reload --- 刷新页面,可以传递true作为参数来强制清除缓存刷新
History --- 浏览器的历史记录,控制浏览器的前进后退
history.back(); --- 后退
history.forward(); --- 前进
history.go(); --- 可前进可后退,参数为正数前进,参数为负数后退
Screen --- 浏览器屏幕的信息
BOM对象都是作为Window对象的属性保存,可以直接在JS中访问这些对象。
定时器: 可以间隔一段时间再执行部分代码。
const timer = setTimeout(() => {},间隔时间); --- 参数一:要执行的函数;参数二:间隔时间(毫秒),只执行一次
clearTimeout(timer); --- 关闭定时器
const timer = setInterval(() => {},间隔时间); --- 参数一:要执行的函数;参数二:间隔时间(毫秒),每间隔一段时间就执行一次
clearInterval(timer); --- 关闭定时器
事件循环(event loop): 函数在每次执行时都会产生一个执行环境,执行环境负责存储函数执行时的一切数据。
调用栈(call stack): 负责存储函数调用时的执行环境。
当一个函数被调用时,它的执行环境会作为一个栈帧插入到调用栈顶部,执行完毕后会从调用栈中自动弹出,后进先出。
消息队列: 负责存储将要执行的函数。
当触发一个事件时,其响应函数并不是直接插入到调用栈中,因为此时调用栈中可能还存在着未执行完的函数。
事件触发后,JS引擎是将事件的响应函数存储在消息队列中排队。
定时器的本质就是在指定间隔时间后将函数存储进消息队列中。
setInterval每间隔一段时间就将函数添加到消息队列中,如果函数执行速度慢,时间多于间隔时间,就会导致后面函数执行间隔时间的不一致。
所以定时器常用setTimeout,为解决该方法只执行一次的缺点,可以采用回调函数进行递归操作。
<body>
<script>
console.time("间隔");
const timer = setTimeout(function fn() {
console.timeEnd("间隔");
console.log("函数执行");
console.time("间隔");
setTimeout(fn, 3000);
}, 3000);
</script>
</body>
可以将函数在调用栈中停几秒的代码:
const time = Date.now();
while(Date.now() - time < 5){};
本文章使用limfx的vscode插件快速发布