DOM
DOM 即 Document Object Model 文档对象模型,浏览器在加载页面是会生成 DOM 对象,以供我们使用 JS 控制页面元素。
文档渲染
浏览器会将 HTML 文本内容进行渲染,并生成相应的 JS 对象,同时会对不符规则的标签进行处理。
- 浏览器会将标签规范后渲染页面
- 目的一让页面可以正确呈现
- 目的二可以生成统一的 JS 可操作对象
操作时机
需要保证浏览器已经渲染了内容才可以读取的节点对象,下例将无法读取到节点对象
<script>
const node = document.getElementById('love')
console.log(node) //null
</script>
<h1 id="love">love</h1>
不过我们可以将脚本通过事件放在页面渲染完执行
<script>
window.onload = () => {
const node = document.getElementById('love')
console.log(node)
}
</script>
<h1 id="love">love</h1>
或使用定时器将脚本设置为异步执行
<script>
setTimeout(() => {
const node = document.getElementById('love')
console.log(node)
})
</script>
<h1 id="love">love</h1>
或将脚本设置在外部文件并使用 defer 属性加载,defer 即会等到 DOM 解析后迟延执行
<script defer="defer" src="love.js"></script>
<div id="love"></div>
节点对象
JS 中操作 DOM 的内容称为节点对象(node),即然是对象就包括操作 NODE 的属性和方法
- 包括 12 种类型的节点对象
- 常用了节点为 document、标签元素节点、文本节点、注释节点
- 节点均继承自 Node 类型,所以拥有相同的属性或方法
- document 是 DOM 操作的起始节点
节点 | nodeType | nodeName | nodeValue | 示例 |
---|---|---|---|---|
元素节点(Element Nodes) | 1 | 大写标签名 | 不包含值或返回null | <div>、<p>、<a> |
属性节点(Attribute Nodes) | 2 | 属性的名称 | 属性的值 | id href class |
文本节点(Text Nodes) | 3 | #text | 内容 | |
CDATA区段节点(CDATASection Nodes) | 4 | 仅存在XML文档中 | ||
注释节点(Comment Nodes) | 8 | #comment | 注释的内容 | |
文档节点(Document Nodes) | 9 | #document | null | |
文档类型节点 | 10 | doctype的名称 | null | <!DOCTYPE html> |
原型链
原型 | 说明 |
---|---|
Object | 根对象,提供 hasOwnProperty 等基本对象操作支持 |
EventTarget | 提供 addEventListener、removeEventListener 等事件支持方法 |
Node | 提供 firstChild、parentNode 等节点操作方法 |
Element | 提供 getElementsByTagName、querySelector 等方法 |
HTMLElement | 所有元素的基础类,提供 childNodes、nodeType、nodeName、className、nodeName 等方法 |
HTMLHeadingElement | Head 标题元素类 |
打印原型链
function prototype(el) {
const prototypes = []
prototypes.push(el.__proto__)
prototypes.push(...(el.__proto__ ? prototype(el.__proto__) : []))
return prototypes
}
对象特征
利用Object.assign
添加或者修改属性。
常用节点
JS 提供了访问常用节点的 api
方法 | 说明 |
---|---|
document | document 是 DOM 操作的起始节点 |
document.documentElement | 文档节点即 html 标签节点 |
document.body | body 标签节点 |
document.head | head 标签节点 |
document.links | 超链接集合 |
document.anchors | 所有锚点集合 |
document.forms | form 表单集合 |
document.images | 图片集合 |
DOCUMENT
document 是 window 对象的属性,是由 HTMLDocument 类实现的实例。
节点属性
使用 data 属性可以获取文本与注释内容
<div id="app">
love.com
<!-- love 注释内容-->
</div>
<script>
const app = document.querySelector('#app')
console.log(app.childNodes[0].data)
console.log(app.childNodes[1].data)
</script>
节点集合
Nodelist 与 HTMLCollection 都是包含多个节点标签的集合,大部分功能也是相同的。
- getElementsByClassName 等方法返回的是 NodeList
- querySelectorAll 返回的是 HTMLCollection
- NodeList 节点列表是动态的,即内容添加后会动态更新
item
Nodelist 与 HTMLCollection 提供了 item()方法来根据索引获取元素,也可以使用数组索引。
<script>
const nodes = document.getElementsByTagName('div')
console.dir(nodes.item(0))
</script>
<script>
const nodes = document.getElementsByTagName('div')
console.dir(nodes[0])
</script>
namedItem
HTMLCollection 具有 namedItem 方法可以按 name 或 id 属性来获取元素
<div name="app">
<div id="love">houdunren.com</div>
<div name="you">houdunwang.com</div>
</div>
<script>
const nodes = document.getElementsByTagName('div')
console.dir(nodes.namedItem('love'))
console.dir(nodes.namedItem('you'))
</script>
数字索引时使用 item 方法,字符串索引时使用 namedItem 或 items 方法
动态与静态
通过 getElementsByTagname 等 getElementsBy... 函数获取的 Nodelist 与 HTMLCollection 集合是动态的,即有元素添加或移动操作将实时反映最新状态。
- 使用 getElement...返回的都是动态的集合
- 使用 querySelectorAll 返回的是静态集合
动态特性
下例中通过按钮动态添加元素后,获取的元素集合是动态的,而不是上次获取的固定快照。
<h1>baidu.com</h1>
<h1>jd.com</h1>
<button id="add">添加元素</button>
<script>
let elements = document.getElementsByTagName('h1')
console.log(elements)
let button = document.querySelector('#add')
button.addEventListener('click', () => {
document.querySelector('body').insertAdjacentHTML('beforeend', '<h1>love you</h1>')
console.log(elements)
})
</script>
document.querySelectorAll 获取的集合是静态的
遍历节点
forOf
Nodelist 与 HTMLCollection 是类数组的可迭代对象所以可以使用 for...of 进行遍历
forEach
Nodelist 节点列表也可以使用 forEach 来进行遍历,但 HTMLCollection 则不可以
call/apply
节点集合对象原型中不存在 map 方法,但可以借用 Array 的原型 map 方法实现遍历
Array.prototype.map.call(nodes, (node, index) => {
console.log(node, index)
})
Array.from
Array.from 用于将类数组转为组件,并提供第二个迭代函数。所以可以借用 Array.from 实现遍历
Array.from(nodes, (node, index) => {
console.log(node, index)
})
节点关系
下面是通过节点关系获取相应元素的方法
节点属性 | 说明 |
---|---|
childNodes | 获取所有子节点 |
parentNode | 获取父节点 |
firstChild | 第一个子节点 |
lastChild | 最后一个子节点 |
nextSibling | 下一个兄弟节点 |
previousSibling | 上一个兄弟节点 |
标签关系
使用 childNodes 等获取的节点包括文本与注释,但这不是我们常用的,为此系统也提供了只操作元素的关系方法。
基础知识
下面是处理标签关系的常用 API
节点属性 | 说明 |
---|---|
parentElement | 获取父元素 |
children | 获取所有子元素 |
childElementCount | 子标签元素的数量 |
firstElementChild | 第一个子标签 |
lastElementChild | 最后一个子标签 |
previousElementSibling | 上一个兄弟标签 |
nextElementSibling | 下一个兄弟标签 |
contains | 返回布尔值,判断传入的节点是否为该节点的后代节点 |
标签获取
getElementById
使用 ID 选择是非常方便的选择具有 ID 值的节点元素,但注意 ID 应该是唯一的,getElementById 只能通过 document 访问。
getElementsByName
使用 getElementByName 获取设置了 name 属性的元素。
- 返回 NodeList 节点列表对象
- NodeList 顺序为元素在文档中的顺序
- 需要在 document 对象上使用
getElementsByTagName
使用 getElementsByTagName 用于按标签名获取元素
- 返回 HTMLCollection 节点列表对象
- 是不区分大小的获取
getElementsByClassName
getElementsByClassName 用于按 class 样式属性值获取元素集合
- 设置多个值时顺序无关,指包含这些 class 属性的元素
样式选择器
querySelectorAll
使用 querySelectorAll 根据 CSS 选择器获取 Nodelist 节点列表
- 获取的 NodeList 节点列表是静态的,添加或删除元素后不变
querySelector
querySelector 使用 CSS 选择器获取一个元素。
matches
用于检测元素是否是指定的样式选择器匹配
closest
查找最近的符合选择器的祖先元素(包括自身)
标准属性
属性别名
有些属性名与 JS 关键词冲突,系统已经起了别名
属性 | 别名 |
---|---|
class | className |
for | htmlFor |
元素特征
对于标准的属性可以使用 DOM 属性的方式进行操作,但对于标签的非标准的定制属性则不可以。但 JS 提供了方法来控制标准或非标准的属性
可以理解为元素的属性分两个地方保存,DOM 属性中记录标准属性,特征中记录标准和定制属性
- 使用特征操作时属性名称不区分大小写
- 特征值都为字符串类型
方法 | 说明 |
---|---|
getAttribute | 获取属性 |
setAttribute | 设置属性 |
removeAttribute | 删除属性 |
hasAttribute | 属性检测 |
特征是可迭代对象,下面使用 for...of 来进行遍历操作
attributes
元素提供了 attributes 属性可以只读的获取元素的属性
<div class="love" data-content="静思">love.com</div>
<script>
let houdunwang = document.querySelector('.love')
console.dir(houdunwang.attributes['data-content'].nodeValue) //静思
console.dir(houdunwang.attributes['data-content'].value) //静思
</script>
自定义特征
虽然可以随意定义特征并使用 getAttribute 等方法管理,但很容易造成与标签的现在或未来属性重名。建议使用以 data-为前缀的自定义特征处理,针对这种定义方式 JS 也提供了接口方便操作。
- 元素中以 data-为前缀的属性会添加到属性集中
- 使用元素的 dataset 可获取属性集中的属性
属性同步
下面对 input 值使用属性设置,但并没有同步到特征
<input type="text" name="package" value="love.com" />
<script>
const package = document.querySelector(`[name='package']`)
package.value = 'love.com'
console.log(package.getAttribute('value'))//love.com
</script>
但改变 input 的特征 value 会同步到 DOM 对象属性
<input type="text" name="package" value="love.com" />
<script>
const package = document.querySelector(`[name='package']`)
package.setAttribute('value', 'love.com')
console.log(package.value) //love.com
</script>
创建节点
创建节点的就是构建出 DOM 对象,然后根据需要添加到其他节点中
createElement
使用 createElement 方法可以标签节点对象>
使用 PROMISE 结合节点操作来加载外部 JAVASCRIPT 文件
function js(file) {
return new Promise((resolve, reject) => {
let js = document.createElement('script')
js.type = 'text/javascript'
js.src = file
js.onload = resolve
js.onerror = reject
document.head.appendChild(js)
})
}
js('outside.js')
.then(() => console.log('加载成功'))
.catch((error) => console.log(`${error.target.src} 加载失败`))
cloneNode&importNode
使用 cloneNode 和 document.importNode 用于复制节点对象操作
- cloneNode 是节点方法
- cloneNode 参数为 true 时递归复制子节点即深拷贝
- importNode 是 documet 对象方法
复制 div#app 节点并添加到 body 元素中
<div id="app">love</div>
<script>
let app = document.querySelector('#app')
let newApp = app.cloneNode(true)
document.body.appendChild(newApp)
</script>
document.importNode 方法是部分 IE 浏览器不支持的,也是复制节点对象的方法
- 第一个参数为节点对象
- 第二个参数为 true 时递归复制
<div id="app">love</div>
<script>
let app = document.querySelector('#app')
let newApp = document.importNode(app, true)
document.body.appendChild(newApp)
</script>
节点内容
innerHTML
inneHTML 用于向标签中添加 html 内容,同时触发浏览器的解析器重绘 DOM。
- innerHTML 中只解析 HTML 标签语法,所以其中的 script 不会做为 JS 处理
outerHTML
outerHTML 与 innerHTML 的区别是包含父标签
textContent 与 innerText
textContent 与 innerText 是访问或添加文本内容到元素中
- textContentb 部分 IE 浏览器版本不支持
- innerText 部分 FireFox 浏览器版本不支持
- 获取时忽略所有标签,只获取文本内容
- 设置时将内容中的标签当文本对待不进行标签解析
设置时将标签当文本对待,即转为 HTML 实体内容
<div id="app">
<div class="love" data="hd">love.com</div>
</div>
<script>
let app = document.querySelector('#app')
app.textContent="<h1>love</h1>" //<h1>love</h1>
</script>
outerText
与 innerText 差别是会影响所操作的标签
insertAdjacentText
将文本插入到元素指定位置,不会对文本中的标签进行解析,包括以下位置
选项 | 说明 |
---|---|
beforebegin | 元素本身前面 |
afterend | 元素本身后面 |
afterbegin | 元素内部前面 |
beforeend | 元素内部后面 |
节点管理
推荐方法
方法 | 说明 |
---|---|
append | 节点尾部添加新节点或字符串 |
prepend | 节点开始添加新节点或字符串 |
before | 节点前面添加新节点或字符串 |
after | 节点后面添加新节点或字符串 |
replaceWith | 将节点替换为新节点或字符串 |
将 h2 移动到 h1 之前
<h1>first.com@h1</h1>
<h2>second@h2</h2>
<script>
let h1 = document.querySelector('h1')
let h2 = document.querySelector('h2')
h1.before(h2)
</script>
insertAdjacentHTML
将 html 文本插入到元素指定位置,浏览器会对文本进行标签解析,包括以下位置
选项 | 说明 |
---|---|
beforebegin | 元素本身前面 |
afterend | 元素本身后面 |
afterbegin | 元素内部前面 |
beforeend | 元素内部后面 |
insertAdjacentElement
insertAdjacentElement() 方法将指定元素插入到元素的指定位置,包括以下位置
- 第一个参数是位置
- 第二个参数为新元素节点
选项 | 说明 |
---|---|
beforebegin | 元素本身前面 |
afterend | 元素本身后面 |
afterbegin | 元素内部前面 |
beforeend | 元素内部后面 |
两者区别就是插入类型不同,insertAdjacentElement
插入的元素需要继承type 'Element'
.
样式管理
通过 DOM 修改样式可以通过更改元素的 class 属性或通过 style 对象设置行样式来完成。
- 建议使用 class 控制样式,将任务交给 CSS 处理,更简单高效
classList
如果对类单独进行控制使用 classList 属性操作
方法 | 说明 |
---|---|
node.classList.add | 添加类名 |
node.classList.remove | 删除类名 |
node.classList.toggle | 切换类名 |
node.classList.contains | 类名检测 |
在元素的原有 class 上添加新 class
<div id="app" class="d-flex container">love</div>
<script>
let app = document.getElementById('app')
app.classList.add('love') //love为类名作为参数
</script>
使用 classList 也可以移除 class 列表中的部分 class
<div id="app" class="d-flex container">love</div>
<script>
let app = document.getElementById('app')
app.classList.remove('container')
</script>
使用 toggle 切换类,即类已经存在时删除,不存在时添加
<div id="app" class="d-flex container">love</div>
<script>
let app = document.getElementById('app')
app.addEventListener('click', function () {
this.classList.toggle('love')
})
</script>
使用 contains 检查 class 是否存在
<div id="app" class="d-flex container">love</div>
<script>
let app = document.getElementById('app')
console.log(app.classList.contains('container')) //true
console.log(app.classList.contains('love')) //false
</script>
设置行样式
使用 style 对象可以对样式属性单独设置,使用 cssText 可以批量设置行样式
样式属性设置
使用节点的 style 对象来设置行样式
app.style.backgroundColor = 'red'
批量设置行样式
使用 cssText 属性可以批量设置行样式,属性名和写 CSS 一样不需要考虑驼峰命名
app.style.cssText = `background-color:red;color:yellow`
app.setAttribute('style', `background-color:red;color:yellow;`)
获取样式
可以通过 style 对象,window.getComputedStyle
对象获取样式属性,下面进行说明
style
可以使用 DOM 对象的 style 属性读取行样式
- style 对象不能获取行样式外定义的样式
console.log(app.style.backgroundColor)
console.log(app.style.margin)
console.log(app.style.marginTop)
console.log(app.style.color)
getComputedStyle
使用 window.getComputedStyle 可获取所有应用在元素上的样式属性
- 函数第一个参数为元素
- 第二个参数为伪类
- 这是计算后的样式属性,所以取得的单位和定义时的可能会有不同
<style>
div {
font-size: 35px;
color: yellow;
}
</style>
<div id="app" style="background-color: red; margin: 20px;">love</div>
<script>
let app = document.getElementById('app')
let style = window.getComputedStyle(app)
console.log(style.fontSize.slice(0, -2))
console.log(parseInt(style.fontSize))
app.style.fontSize = parseInt(style.fontSize) + 21 + 'px'
console.log(parseInt(style.fontSize))
</script>