JavaScript中DOM操作
# DOM和BOM
DOM(文档对象模型),将页面所有的内容表示为可以修改的对象
BOM(浏览器对象模型),由浏览器提供的用于处理文档之外的所有内容的其它对象,比如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元素,还有很多其他的子元素,这些结构最终形成了一个树结构,在抽象成DOM对象的时候,也会形成一个树结构,称之为DOM Tree
# DOM 的继承关系图
DOM相当于是JavaScript和HTML,CSS之间的桥梁,通过浏览器提供的DOM API,可以对元素以及其中的内容做任何事情。
# document对象
Document节点表示的整个载入的网页,他的实例是全局的document对象。对DOM的所有操作都是从document 开始的;它是DOM的入口点,可以从document开始去访问任何节点元素。
对于最顶层的html、head、body元素,可以直接在document对象中获取到
- html 元素:document.documentElement
<html>
- body 元素:document.body
<body>
- head 元素:document.head
<head>
- 文档声明:document.doctype
<!DOCTYPE html>
console.log('doctype: ', document.doctype)
console.log('html: ', document.documentElement)
console.log('head: ', document.head)
console.log('body: ', document.body)
2
3
4
# 节点(Node)之间的导航(navigator)
如果获取到一个节点后,可以根据这个节点去获取其它的节点,称之为节点之间的导航
节点之间存在如下的关系
- 父节点:parentNode
- 前兄弟节点:previousSibling
- 后兄弟节点:nextSibling
- 子节点:childNodes
- 第一个子节点:firstChild
- 最后一个子节点:lastChild
// 获取所有的节点的导航
var bodyEl = document.body
// 获取body所有的子节点
console.log(bodyEl.childNodes)
// 获取body的第一个子节点
console.log(bodyEl.firstChild)
// 获取body中的注释
console.log(bodyEl.firstChild.nextSibling)
// 获取body的父节点
console.log(bodyEl.parentNode)
2
3
4
5
6
7
8
9
10
# 元素之间的导航
如果获取到一个元素后,可以根据这个元素去获取其它的元素,称之为元素之间的导航
元素之间存在如下的关系
- 父元素:parentElement
- 前兄弟元素:previousElementSibling
- 后兄弟元素:nextElementSibling
- 子元素:children
- 第一个子元素:firstElementChild
- 最后一个子元素:lastElementChild
const bodyEl = document.body
// 根据body获取子元素
console.log(bodyEl.children)
// 获取第一个子元素
var firstChildElement1 = bodyEl.firstElementChild
var firstChildElement2 = bodyEl.children[0]
console.log(firstChildElement1)
console.log(firstChildElement2)
// 获取第二个子元素
const nextEle = firstChildElement1.nextElementSibling
console.log(nextEle)
// 假设第二个子元素为ul,那么接下来获取里面的所有li元素
var liEls = nextEle.children
console.log(liEls)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 表格元素的导航
table
元素除了支持上面给出的属性之外,还支持以下属性
- table.rows -
<tr>
元素集合 - table.caption/thead/tfoot - 引用元素
<caption>
,<thead>
,<tfoot>
- table.tbodies -
<tbody>
元素的集合
thead, tfoot, tbody 提供了rows
属性
- tbody.rows - 表格内部的
<tr>
元素的集合
tr
- tr.ceils - 在给定的tr中的td和th单元格集合
- tr.sectionRowIndex - 给定的 tr 在封闭的
<thead>/<tbody>/<tfoot>
中的位置(索引) - tr.rowIndex - 在整个表格中 tr 的编号 (包括表格的所有行)
td 和 th
- td.cellIndex - 在封闭的
<tr>
中单元格的编号
# 获取表单元素以及表单元素之间的导航
form元素可以直接通过document.forms
获取所有的form元素,form元素中的内容可以通过form.elements
来获取。
// 获取form元素
var formEl = document.forms[0]
// 通过form.elements获取form中的内容
var elements = formEl.elements
2
3
4
可以设置表单子元素的name来获取
<form action="">
<input type="text" name="account" />
<input type="password" type="password" />
<input type="checkbox" type="sex" checked />
<select name="fruits" id="fruits">
<option value="apple">苹果</option>
<option value="orange">橘子</option>
</select>
</form>
<script>
var formEl = document.forms[0];
var elements = formEl.elements;
console.log(elements.account.value);
console.log(elements.fruits.value);
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 获取元素的方法
当元素彼此靠近或者相邻时,DOM导航属性非常有用,但是在实际开发中,想要获取到某一个元素该如何操作。
DOM提供了如下获取元素的方法
方法名 | 搜索方式 | 是否可以在元素上调用 | 是否实时的 |
---|---|---|---|
querySelector | css-selector | 是 | 否 |
querySelectorAll | css-selector | 是 | 否 |
getElementById | id | 否 | 否 |
getElementsByName | name | 否 | 是 |
getElementsByTagName | tag or * | 是 | 是 |
getElementsByClassName | class | 是 | 是 |
开发中的选择
- 目前最经常使用的querySelector和querySelectorAll
- getElementById偶尔使用或者适配低版本的浏览器
# 节点属性--nodeType
nodeType属性提供了一种获取节点类型的方法,它有一个数值类型
常量 | 值 | 描述 |
---|---|---|
Node.ELEMENT_NODE | 1 | 一个 元素 节点,例如<p> 和<div> 。 |
Node.TEXT_NODE | 3 | Element 或者 Attr 中实际的 文字 |
Node.COMMENT_NODE | 8 | 一个 Comment 节点。 |
Node.DOCUMENT_NODE | 9 | 一个 Document 节点。 |
Node.DOCUMENT_TYPE_NODE | 10 | 描述文档类型的 DocumentType 节点。例如 <!DOCTYPE html> 就是用于 HTML5 的。 |
文档地址
https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType
# 节点的属性 -- nodeName,tagName
nodeName: 获取node节点的名字,tagName: 获取元素的标签名称
nodeName和tagName之间的不同点
- tagName属性仅适用于Element节点
- nodeName是为任意Node定义的,对于元素,它的意义与 tagName 相同,所以使用哪一个都是可以的;对于其他节点类型(text,comment 等),它拥有一个对应节点类型的字符串;
# 节点属性 -- innerHTML,textContent、data
# innerHTML属性
- 将元素中的 HTML 获取为字符串形式;
- 设置元素中的内容;
# outerHTML属性
- 包含了元素的完整 HTML
- innerHTML 加上元素本身一样;
# textContent属性
- 仅仅获取元素中的文本内容;
# innerHTML和textContent的区别
- 使用 innerHTML,我们将其“作为 HTML”插入,带有所有 HTML 标签。
- 使用 textContent,我们将其“作为文本”插入,所有符号(symbol)均按字面意义处理
# 节点属性 -- nodeValue
nodeValue/data 用于获取非元素节点的文本内容
var text = document.body.firstChild
console.log(text.nodeValue)
2
# data(nodeValue)
data是针对于非元素的节点,元素节点获取的结果是 null
# 节点其它属性
hidden
属性,是一个全局属性,可以用于设置元素隐藏
var box = document.querySelector('.box')
box.hidden = true
2
DOM 元素的其它属性
- value
<input>
,<select>
和<textarea>
(HTMLInputElement,HTMLSelectElement……)的 value。
- href
<a href="...">
(HTMLAnchorElement)的 href。
- id
- 所有元素(HTMLElement)的 “id” 特性(attribute)的值。
# 元素的属性和特性
一个元素除了有开始标签,结束标签,内置之外还有很多属性
浏览器在解析HTML元素时,会将对应的attribute也创建出来放到对应的元素对象上。比如 id、class就是全局的attribute,会有对应的id, class属性。
# attribute的分类
- 标准的attribute: 某些attribute属性是标准的,比如id, class, href, type, value
- 非标准的attribute: 某些attribute属性是自定义的,比如abc、age、height等
# attribute的操作
- ele.hasAttribute(name) 检查特性是否存在
- ele.getAttribute(name) 获取这个特性值
- ele.setAttribute(name, value) 设置这个特性值
- ele.removeAttribute(name) 移除这个特性
- attributes: attr对象的集合,具有name, value属性
for(var attr of boxEl.attributes){
console.log(attr.name, attr.value)
}
console.log(boxEl.hasAttribute('age'))
console.log(boxEl.getAttribute('name'))
boxEl.setAttribute('name', 'abc')
boxEl.removeAttribute('abc')
2
3
4
5
6
7
attribute具备以下的特征
- 他们的名字是大小写不敏感的(id与ID相同)
- 他们的值总是字符串类型的
# 元素的属性 property
对于标准的attribute,会在DOM对象上创建与其对应的property属性
console.log(boxEl.id, boxEl.className)
console.log(boxEl.abc, boxEl.age, boxEl.height)
2
在大多数情况下,他们是相互作用的,改变property,通过attribute获取的值,会随着改变。通过attribute操作修改,property的值会随着改变,但是有些浏览器的input的value修改只能通过attribute的方法,目前大部分浏览器都是已经支持的。通过el.value
的方式设置值的优先级更高。
除非特殊情况下,大多数情况下,设置、获取attribute,推荐使用property的方式,因为它默认情况下是有类型的。
btn.onclick = function(){
checkBoxInput.checked = !checkBoxInput.checked
}
2
3
# HTML5 的data-* 自定义属性
HTML5中的data-*自定义属性也是可以在dataset属性中获取到的
<div class="box" data-name="b1" data-age="18">
</div>
<script>
var boxEl = document.querySelector('.box')
console.log(boxEl.dataset.name)
console.log(boxEl.dataset.age)
</script>
2
3
4
5
6
7
8
# JavaScript动态修改样式
有时候会通过JavaScript来动态修改样式,这时候有两个选择
在CSS中编写好对应的样式,动态添加 class
动态修改style属性
在开发中大多数情况下可以动态修改class完成某个功能,因此更推荐使用动态class
对于某些情况如果无法动态修改class(比如精准修改某个css属性的值),那么就可以修改style属性
# 元素的className和classList
元素的attribute,对应的property并非叫class,而是className。因为JavaScript早起不允许使用class关键字来作为对象的属性,所以DOM规范使用了className
,虽然想在JavaScript已经没有了这样的限制,但是并不推荐,并且依然在使用className这个名称。
可以对className进行赋值,会替换整个类中的字符串
var boxEl = document.querySelector('.box')
boxEl.className = 'anc'
2
如果需要添加或者移除单个class,那么可以使用classList属性
ele.classList是一个特殊的对象
- ele.classList.add(class) 添加一个类
- ele.classList.remove(class) 添加/移除类
- ele.classList.toggle(class) 如果类不存在就添加类,存在就移除它
- ele.classList.contains(class) 检查给定的类,返回true/false
classList是可迭代对象,可以通过for...of..
进行遍历
# 元素的style属性
如果需要单独修改某一个css属性,那么可以通过style来操作,对于多词(multi-word)属性,使用驼峰式
boxEl.style.width = '100px'
boxEl.style.height = '50px'
boxEl.style.backgroundColoe = 'red'
2
3
如果将值设置为空字符串,那么会使用CSS的默认样式
boxEl.style.display = ''
多个样式的写法,需要使用cssText
属性,不推荐这种用法,因为他会替换整个字符串
boxEl.style.cssText = `width: 100px; height: 100px; background-color: red;`
# 元素style的读取 -- getComputedStyle
如果需要读取样式,
- 对于内联样式,是可以通过
style.*
方式读取到的 - 对于style、css文件中的样式是读取不到的
对于读取不到的情况可以通过getComputedStyle
的全局函数来实现
console.log(getComputedStyle(boxEl).width)
console.log(getComputedStyle(boxEl).height)
console.log(getComputedStyle(boxEl).backgroundColor)
2
3
# 创建元素
可以使用document.write
方法写入一个元素,这种方式写起来非常便捷,但是对于复杂的内容、元素关系拼接并不方便。它是在早期没有DOM的时候使用的方案,目前依然被保留了下来。
目前我们想要插入一个元素,通常会按照如下步骤
- 创建一个元素
- 插入元素到DOM的某一个位置;
创建元素
var boxE = document.querySelector('.box')
var h2El = document.createElement('h2')
h2El.innerHTML = '二级标题'
h2El.classList.add('title')
boxEl.append(h2El)
2
3
4
5
# 插入元素
- 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 替换为给定的节点或字符串
# 移除和克隆元素
移除元素可以使用元素本身的remove
方法,移除自身以及子元素
h2El.remove()
如果需要复制一个现有的元素,可以通过cloneNode
方法
cloneNode
的使用
- 可以传入一个Boolean类型的值来决定是否是深度克隆
- 深度克隆会克隆对应的子元素,否则不会
var cloneBoxEl = boxEl.cloneNode(true)
document.body.append(cloneBoxEl)
2
# 旧的元素的操作方法
- parentElem.appendChild(node) 在parentElem的父元素最后位置添加一个子元素
- parentElem.insertBefore(node, nextSibling) 在parentElem的nextSibling前面插入一个子元素
- parentElem.replaceChild(node, oldChild) 在parentElem中,新元素替换之前的oldChild元素
- parentElem.removeChild(node) 在parentElem中,移除某一个元素
# 元素的大小、滚动
- clientWisth: contentWidth + padding (不包含滚动条)
- cilentHeight:contentHeight + padding
- clientTop:border-top边框的宽度
- clientLeft:border-left边框的宽度
- offsetWidth:元素完整的宽度
- offsetHeight:元素完整的高度
- offsetLeft:左侧距离父元素的宽度
- offsetTop:顶部距离父元素的宽度
- 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) 将页面滚动至 绝对坐标
# 倒计时案例
var endDate = new Date()
endDate.setHours(24)
endDate.setMinutes(0)
endDate.setSeconds(0)
endDate.setMilliseconds(0)
var start = new Date()
vat intervalTime = Math.floot((endDate.getTime() - startDate.getTime())/1000)
var hour = Math.floot(intervalTime / 3600)
var minute = Math.floor(intervalTime / 60) % 60
var second = intervalTime % 60
2
3
4
5
6
7
8
9
10
11