JavaScript的DOM操作
元素的继承
class Student extends Person{} 创建一个Student对象继承自Person对象本身会自带Person的属性并且可以创建属于自己的属性
什么是DOM?
◼ 前面我们花了很多时间学习JavaScript的基本语法,但是这些基本语法,但是这些语法好像和做网页没有什么关系,和前面学习
的HTML、CSS也没有什么关系呢?
这是因为我们前面学习的部分属于ECMAScript,也就是JavaScript本身的语法部分;
除了语法部分之外,我们还需要学习浏览器提供给我们开发者的DOM、BOM相关的API才能对页面、浏览器进行操作;
◼ 前面我们学习了一个window的全局对象,window上事实上就包含了这些内容:
我们已经学习了JavaScript语法部分的Object、Array、Date等;
另外还有DOM、BOM部分;
◼ DOM:文档对象模型(Document Object Model)
简称DOM,将页面所有的内容表示为可以修改的对象;
◼ BOM:浏览器对象模型(Browser Object Model)
简称BOM,由浏览器提供的用于处理文档(document)之外的所有内容的其他对象;
比如navigator、location、history等对象;
深入理解DOM
浏览器会对我们编写的HTML、CSS进行渲染,同时它又要考虑我们可能会通过JavaScript来对其进行操作:
于是浏览器将我们编写在HTML中的每一个元素(Element)都抽象成了一个个对象;
所有这些对象都可以通过JavaScript来对其进行访问,那么我们就可以通过JavaScript来操作页面;
所以,我们将这个抽象过程称之为文档对象模型(Document Object Model);
◼ 整个文档被抽象到document 对象中:
比如document.documentElement对应的是html元素;
比如document.body对应的是body元素;
比如document.head对应的是head元素;
◼ 下面的一行代码可以让整个页面变成红色:
document.body.style.backgroundColor = "red"
◼ 所以我们学习DOM,就是在学习如何通过JavaScript对文档进行操作的;
认识DOM Tree
◼ 一个页面不只是有html、head、body元素,也包括很多的子元素:
在html结构中,最终会形成一个树结构;
在抽象成DOM对象的时候,它们也会形成一个树结构,我们称之为DOM Tree;
document对象
◼ Document节点表示的整个载入的网页,它的实例是全局的document对象:
对DOM的所有操作都是从document 对象开始的;
它是DOM的入口点,可以从document开始去访问任何节点元素;
◼ 对于最顶层的html、head、body元素,我们可以直接在document对象中获取到:
html元素:<html> = document.documentElement
body元素:<body> = document.body
head元素:<head> = document.head
文档声明:<!DOCTYPE html> = document.doctype
DOM的整体结构
◼ DOM相当于是JavaScript和HTML、CSS之间的桥梁
通过浏览器提供给我们的DOM API,我们可以对元素以及其中的内容做任何事情;
var htmlEl = document.documentElement;
var bodyEl = document.body
var headEl = document.head
var docTypeEl = document.doctype
console.log(htmlEl,bodyEl,headEl,docTypeEl)
节点、元素导航
节点(Node)之间的导航(navigator)
◼ 如果我们获取到一个节点(Node)后,可以根据这个节点去获取其他的节点,我们称之为节点之间的导航。
◼ 节点之间存在如下的关系:
父节点:parentNode
前兄弟节点:previousSibling
后兄弟节点:nextSibling
子节点:childNodes
第一个子节点:firstChild
最后一个子节点:lastChild
元素(Element)之间的导航(navigator)
◼ 如果我们获取到一个元素(Element)后,可以根据这个元素去获取其他的元素,我们称之为元素之间的导航。
◼ 节点之间存在如下的关系:
父元素:parentElement
前兄弟元素:previousElementSibling
后兄弟元素:nextElementSibling
子元素:children
第一个子元素:firstElementChild
最后一个子元素:lastElementChild
表格(table)元素的导航(navigator)
◼ <table> 元素支持 (除了上面给出的,之外) 以下这些属性:
table.rows — <tr> 元素的集合;
table.caption/tHead/tFoot — 引用元素 <caption>,<thead>,<tfoot>;
table.tBodies — <tbody> 元素的集合;
◼ <thead>,<tfoot>,<tbody> 元素提供了 rows 属性:
tbody.rows — 表格内部<tr> 元素的集合;
◼ <tr>:
tr.cells — 在给定 <tr> 中的 <td> 和 <th> 单元格的集合;
tr.sectionRowIndex — 给定的 <tr> 在封闭的 <thead>/<tbody>/<tfoot> 中的位置(索引);
tr.rowIndex — 在整个表格中 <tr> 的编号(包括表格的所有行);
◼ <td> 和 <th>:
td.cellIndex — 在封闭的 <tr> 中单元格的编号。
表单(form) 元素导航
1.form 元素可以直接通过document来获取:document.forms
var formEl = document.forms[0]
2.form 元素中的内容可以通过elements来获取
var elements = formEl.elements
我们可以通过设置表单元素的name来获取它们
<form action="">
<input type="text" name="account" >
<input type="password" name="password" autocomplete="off">
<input type="checkbox" name="hobbies" checked>
<select name="fruits" >
<option value="apple">苹果</option>
<option value="orange">橘子</option>
</select>
</form>
<script>
// 获取form元素
// var formEl =document.body.firstElementChild
var formEl = document.forms[0]
// 获取form中的子元素
var inputEl = formEl.elements.account
//
setTimeout(function(){
console.log(inputEl.value)
},2000)
</script>
获取元素的方法
◼ 当元素彼此靠近或者相邻时,DOM 导航属性(navigation property)非常有用。
但是,在实际开发中,我们希望可以任意的获取到某一个元素应该如何操作呢?
方法名 搜索方式 可以在元素上调用? 实时的?
querySelector CSS-selector ✔ -
querySelectorAll CSS-selector ✔ -
getElementById id - -
getElementsByName name - ✔
getElementsByTagName tag or '*' ✔ ✔
getElementsByClassName class ✔ ✔
Node节点的属性
◼ 目前,我们已经可以获取到节点了,接下来我们来看一下节点中有哪些常见的属性:
当然,不同的节点类型有可能有不同的属性;
这里我们主要讨论节点共有的属性;
◼ nodeType属性:
nodeType 属性提供了一种获取节点类型的方法;
它有一个数值型值(numeric value);
其他类型可以查看MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType
◼ nodeName:获取node节点的名字;
◼ tagName:获取元素的标签名词;
◼ tagName和nodeName之间有什么不同呢?
tagName属性仅适用于Element 节点;
nodeName是为任意Node 定义的:
✓ 对于元素,它的意义与tagName相同,所以使用哪一个都是可以的;
✓ 对于其他节点类型(text,comment 等),它拥有一个对应节点类型的字符串;
节点的属性-innerHTML、textContent
◼ innerHTML 属性
将元素中的HTML 获取为字符串形式;
设置元素中的内容;
◼ outerHTML 属性
包含了元素的完整HTML
innerHTML 加上元素本身一样;
◼ textContent 属性
仅仅获取元素中的文本内容;
◼ innerHTML和textContent的区别:
使用innerHTML,我们将其“作为 HTML”插入,带有所有HTML 标签。
使用textContent,我们将其“作为文本”插入,所有符号(symbol)均按字面意义处理。
◼ nodeValue/data
用于获取非元素节点的文本内容
节点的其他属性
◼ hidden属性:也是一个全局属性,可以用于设置元素隐藏。
◼ DOM 元素还有其他属性:
value
✓ <input>,<select> 和 <textarea>(HTMLInputElement,HTMLSelectElement……)的 value。
href
✓ <a href="...">(HTMLAnchorElement)的 href。
id
✓ 所有元素(HTMLElement)的“id” 特性(attribute)的值。
◼ class和style我们会在后续专门讲解的
元素的特性attribute
◼ 前面我们已经学习了如何获取节点,以及节点通常所包含的属性,接下来我们来仔细研究元素Element。
◼ 我们知道,一个元素除了有开始标签、结束标签、内容之外,还有很多的属性(attribute)
◼ 浏览器在解析HTML元素时,会将对应的attribute也创建出来放到对应的元素对象上。
比如id、class就是全局的attribute,会有对应的id、class属性;
比如href属性是针对a元素的,type、value属性是针对input元素的;
◼ 接下来我们学习一下如何获取和设置这些属性。
attribute的分类
◼属性attribute的分类:
标准的attribute:某些attribute属性是标准的,比如id、class、href、type、value等;
非标准的attribute:某些attribute属性是自定义的,比如abc、age、height等;
attribute的操作
◼ 对于所有的attribute访问都支持如下的方法:
elem.hasAttribute(name) — 检查特性是否存在。
elem.getAttribute(name) — 获取这个特性值。
elem.setAttribute(name, value) — 设置这个特性值。
elem.removeAttribute(name) — 移除这个特性。
attributes:attr对象的集合,具有name、value属性;
◼ attribute具备以下特征:
// 1.所有的attribute都支持的操作
console.log(boxEl.hasAttribute("age"),boxEl.hasAttribute("abc"),boxEl.hasAttribute("id"))
console.log(boxEl.getAttribute("age"),boxEl.getAttribute("abc"),boxEl.getAttribute("id"))
boxEl.setAttribute("id","cba")
boxEl.removeAttribute("id")
var boxArributes = boxEl.attributes
for (var attr of boxArributes){
console.log(attr.name,attr.value)
}
它们的名字是大小写不敏感的(id 与ID 相同)。
它们的值总是字符串类型的。
元素的属性property
◼ 对于标准的attribute,会在DOM对象上创建与其对应的property属性:
// 获取box元素
var boxEl = document.querySelector(".box")
console.log(boxEl.id,boxEl.title,boxEl.age,boxEl.height)
var inputEl = document.querySelector("input")
console.log(inputEl.checked)
if(inputEl.checked){
console.log(`checkbox处于选中状态`)
}
◼ 在大多数情况下,它们是相互作用的
改变property,通过attribute获取的值,会随着改变;
通过attribute操作修改,property的值会随着改变;
✓ 但是input的value修改只能通过attribute的方法;
◼ 除非特别情况,大多数情况下,设置、获取attribute,推荐使用property的方式:
这是因为它默认情况下是有类型的
元素的class、style
◼ 有时候我们会通过JavaScript来动态修改样式,这个时候我们有两个选择:
选择一:在CSS中编写好对应的样式,动态的添加class;
选择二:动态的修改style属性;
◼ 开发中如何选择呢?
在大多数情况下,如果可以动态修改class完成某个功能,更推荐使用动态class;
如果对于某些情况,无法通过动态修改class(比如精准修改某个css属性的值),那么就可以修改style属性;
// 1.获取box
var boxEl = document.querySelector(".box")
console.log(boxEl)
boxEl.onclick = function(){
// 直接修改style属性
// boxEl.style.color = "red"
// boxEl.style.fontSize = "24px"
// boxEl.style.backgroundColor = "green"
//2. 动态添加class
boxEl.className = "active"
}
元素的className和classList
◼ 元素的class attribute,对应的property并非叫class,而是className:
这是因为JavaScript早期是不允许使用class这种关键字来作为对象的属性,所以DOM规范使用了className;
虽然现在JavaScript已经没有这样的限制,但是并不推荐,并且依然在使用className这个名称;
◼ 我们可以对className进行赋值,它会替换整个类中的字符串。
var boxEl = document.querySelector(".box")
// 1.方法一:className
boxEl.className = "abc"
var obj = {}
◼ 如果我们需要添加或者移除单个的class,那么可以使用classList属性。
◼ elem.classList 是一个特殊的对象:
elem.classList.add (class) :添加一个类
elem.classList.remove(class):添加/移除类。
elem.classList.toggle(class) :如果类不存在就添加类,存在就移除它。
elem.classList.contains(class):检查给定类,返回 true/false。
◼ classList是可迭代对象,可以通过for of进行遍历。
元素的style属性
◼ 如果需要单独修改某一个CSS属性,那么可以通过style来操作:
对于多词(multi-word)属性,使用驼峰式camelCase
boxEl.style.backgroundColor = "red"
◼ 如果我们将值设置为空字符串,那么会使用CSS的默认样式:
◼ 多个样式的写法,我们需要使用cssText属性:
boxEl.style.cssText = `
width:100px;
height:100px;
background-color:red;
`
不推荐这种用法,因为它会替换整个字符串
元素style的读取- getComputedStyle
◼ 如果我们需要读取样式:
对于内联样式,是可以通过style.*的方式读取到的;
对于style、css文件中的样式,是读取不到的;
◼ 这个时候,我们可以通过getComputedStyle的全局函数来实现:
var boxEl = document.querySelector(".box")
console.log(boxEl.style.backgroundColor)
console.log(getComputedStyle(boxEl).fontSize)
元素的常见操作
创建元素
◼ 前面我们使用过document.write 方法写入一个元素:
这种方式写起来非常便捷,但是对于复杂的内容、元素关系拼接并不方便;
它是在早期没有DOM的时候使用的方案,目前依然被保留了下来;
◼ 那么目前我们想要插入一个元素,通常会按照如下步骤:
步骤一:创建一个元素;
步骤二:插入元素到DOM的某一个位置;
◼ 创建元素: document.createElement(tag)
var boxEl = document.querySelector(".box")
var h2El = document.createElement("h2")
h2El.innerHTML = "我是标题"
h2El.classList.add("title")
boxEl.append(h2El)
插入元素
◼ 插入元素的方式如下:
node.append(...nodes or strings) —— 在 node 末尾 插入节点或字符串 ----->子元素的最后,
node.prepend(...nodes or strings) —— 在 node 开头 插入节点或字符串 ---->子元素的最前方,
node.before(...nodes or strings) —— 在 node 前面 插入节点或字符串 ----->变成兄弟元素放在前面,
node.after(...nodes or strings) —— 在 node 后面 插入节点或字符串------->变成兄弟元素放在后面,
node.replaceWith(...nodes or strings) —— 将 node 替换为给定的节点或字符串。
案例:
// 将元素插入到boxEl里面
// 方法一:追加到子元素最后
boxEl.append(h2El)
// 追加到子元素最前面
boxEl.prepend(h2El)
// 变成兄弟元素,放在前面
boxEl.before(h2El)
// 变成兄弟元素,放在后面
boxEl.after(h2El)
// 替换
boxEl.replaceWith(h2El)
移除和克隆元素
◼ 移除元素我们可以调用元素本身的remove方法:
setTimeout(()=>{
h2El.remove()
},2000)
◼ 如果我们想要复制一个现有的元素,可以通过cloneNode方法:
可以传入一个Boolean类型的值,来决定是否是深度克隆;
深度克隆会克隆对应元素的子元素,否则不会
案例:
var cloneBoxEl = boxEl.cloneNode(true)
document.body.append(cloneBoxEl)
旧的元素操作方法
◼ 在很多地方我们也会看到一些旧的操作方法:
parentElem.appendChild(node):
✓ 在parentElem的父元素最后位置添加一个子元素
parentElem.insertBefore(node, nextSibling):
✓ 在parentElem的nextSibling前面插入一个子元素;
parentElem.replaceChild(node, oldChild):
✓ 在parentElem中,新元素替换之前的oldChild元素;
parentElem.removeChild(node):
✓ 在parentElem中,移除某一个元素;
元素的大小和滚动
◼ clientWidth:contentWith+padding(不包含滚动条)
◼ clientHeight:contentHeight+padding
◼ clientTop:border-top的宽度
◼ clientLeft:border-left的宽度
◼ offsetWidth:元素完整的宽度
◼ offsetHeight:元素完整的高度
◼ offsetLeft:距离父元素的x
◼ offsetTop:距离父元素的y
◼ scrollHeight:整个可滚动的区域高度
◼ scrollTop:滚动部分的高度
window的大小和滚动
◼ window的width和height
innerWidth、innerHeight:获取window窗口的宽度和高度(包含滚动条)
outerWidth、outerHeight:获取window窗口的整个宽度和高度(包括调试工具、工具栏)
documentElement.clientHeight、documentElement.clientWidth:获取html的宽度和高度(不包含滚动条)
◼ window的滚动位置:
scrollX:X轴滚动的位置(别名pageXOffset)
scrollY:Y轴滚动的位置(别名pageYOffset)
◼ 也有提供对应的滚动方法:
方法scrollBy(x,y) :将页面滚动至 相对于当前位置的 (x, y) 位置;
方法scrollTo(pageX,pageY) 将页面滚动至 绝对坐标