文档对象模型DOM是html和xml文档的编程接口
DOM表示由多层节点构成的文档,开发者可以自行添加,删除和修改
14.1 节点层级
html可以表示为层级结构
document是每个文档的根节点
14.1.1 Node类型
Node接口是所有DOM节点类型都必须实现的
这个接口在JS中被实现为Node类型,所有节点都继承自Node类型
nodeName和nodeValue取决于节点类型
每个节点都有一个childNodes属性,返回NodeList,可以使用中括号或item()访问里面的属性
使用Array.prototype.slice()/Array.from()可以将NodeList转化为数组
ownerDocument属性是一个指向代表整个文档的文档节点指针
操作节点
appendChild() 在childNodes末尾增加子节点
如果增加的节点是原来就有的,那么会把它转移到末尾
insertBefore() 第二个参数跟参照节点,为null则和appendChild()相同
replaceChild()
removeChild()
14.1.2 Document类型
文档对象document是HTMLDocument的实例,表示整个document页面
Document类型节点的特征:
nodeType = 9
nodeName = "#document"
nodeValue parentNode ownerDocument = null
1.文档子节点
document.documentElement 始终指向<html>
document.body 指向<body>
出现在<html>元素外边的注释也是文档的子节点,类型为Comment;但是根据浏览器,这些注释不一定能被识别
2.文档信息
document.title 是个只读属性 表示<title>元素中的文本
URL获取URL
domain获取域名
referrer//取得来源
3.定位元素
getElementById() 获取首个id相同的元素,可能不区分大小写
getElementByTagName() 取得所有相同的标签名的元素,返回NodeList(HTMLCollection)对象
getElementByName() 取得所有相同的name的元素
4.特殊集合
document.anchors 返回包含所有带name属性的<a>
document.forms 返回包含所有<form>
document.images 返回包含所有<img>
document.links 返回包含所有带href属性的<a>
5.DOM对象兼容性检测
hasFeature()
6.文档写入
write() 写入一个字符串
writeln() 写入一个字符串并添加'\n'
open() 打开网页输出流
close() 关闭网页输出流
14.1.3 Element类型
Element表示XML或HTML元素,对外暴露出访问元素标签名,子节点和属性的能力
Element类型节点特征:
nodeType = 1
nodeName 为元素标签名 也可以用tagName
nodeValue = null
parentNode 为 Document或Element对象
1.HTML元素
所有HTML元素都通过HTMLElement类型表示
HTMLElement新增了一些属性:
id 元素在文档内的唯一标识符
title 元素额外信息
lang 元素的语言
dir 元素的书写方案
className 相当于class,用于指定CSS类
2.取得元素
getAttribute()
参数:
"id" "class" "title" "lang" "dir"
3.设置元素
setAttribute()
第一个参数同上,第二个参数是新的元素值
设置的元素名会规范为小写
removeAttribute() 移除元素
4.attributes属性
Element类型是唯一一个使用attributes属性的DOM类型
attributes包含一个NamedNodeMap实例,元素的每个属性表示为Attr节点,并保存在这个对象中
NamedNodeMap包含下列方法
getNameItem(name) 返回nodeName属性等于name的节点
removeNameItem(name) 删除nodeName属性等于name的节点
setNameItem(node) 向列表中添加node节点,以其nodeName为索引
item(pos) 返回索引位置pos处的节点
5.创建元素
createElement()
参数为要设置成为的标签名
创建完后可以为其添加属性
最后在一插入节点的方法把元素添加到文档中
6.元素后代
childrenNodes属性包含元素所有的子节点
可以使用getElementByTagName()把只返回当前节点的后代
let ul = document.getElementById("myList")
let items = ul.getElementsByTagNameNS("li")
如果节点包含多层级,那么getElementByTagName()会把所有层级的节点都返回
14.1.4 Text类型
Text包含纯文本或者转义后的HTML字符
Text类型节点特征:
nodeType = 3
nodeName = "#text"
nodeValue 为包含的文本
parentNode 为Element对象
无子节点
包含的文本可以通过nodeValue访问,也可以通过data访问,可读写
1.创建文本节点
createTextNode()
let element = document.createElement("div")
element.id = 'div'
let text = document.createTextNode("Hello World!!!")
element.appendChild(text)
document.body.appendChild(element)
2.规范化文本节点
normalize()
把所有同胞节点合并成为一个文本节点
3.拆分文档节点
splitText()
从指定位置拆分nodeValue,把一个文本节点拆成两个
常用于从文本节点中提取数据的DOM解析技术
14.1.5 Comment类型
Comment就是DOM中的注释
Comment类型节点特征:
nodeType = 8
nodeName = "#comment"
nodeValue 为注释的内容
parentNode 为Element或Document对象
无子节点
Comment类型和Text类型都继承自同一个基类(characterDate)
拥有除了splitText()之外的Text节点的所有方法
浏览器不承认</html>之后的注释节点,除非明确表示它们是<html>的后代
14.1.6 CDATASection类型
CDATASection表示在XML中特有的CDATA区块,继承自Text类型,基本和Text类型相同
拥有Text类型的所有方法
但是CDATA区块只在XML文档中有效
14.1.7 DocumentType类型
DocumentType包含文档的文档类型(doctype)信息
DocumentType类型节点特征:
nodeType = 10
nodeName = 文档类型名称
nodeValue 为 null
parentNode 为Document'对象
无子节点
无论如何,只有name属性是有效的,包含文档类型的名称,即紧跟在<!DOCTYPE 后面的文本
14.1.8 DocumentFragment类型
DocumentFragment类型是文档片段,被DOM定义为"轻量级文档",能够包含和操作节点,但是却没有完整文档那样的消耗
不能直接把文档片段添加到文档,文档片段的作用是充当其他要被添加到文档的节点的仓库
let fragment = document.createDocumentFragment()
let ul = document.getElementById("MyList")
for(let i=0;i<3;i++){
let li = document.createElement("li")
li.appendChild(document.createTextNode(`Item ${i}`))
fragment.appendChild(li)
}
ul.appendChild(fragment)
14.1.9 Attr类型
元素数据在DOM中通过Attr表示
Attr类型节点特征:
nodeType = 2
nodeName 为 属性名
nodeValue 为 属性值
parentNode 为 null
无子节点
属性节点虽然是节点,但是很少被引用,开发者更喜欢用getAttribute() setAttribute()等方法操作属性
14.2 DOM编程
14.2.1 动态脚本
<script>元素可以向网页中插入JS代码
动态脚本就是页面初始加载的时候不存在,之后又通过DOM包含的脚本
整个过程可以抽象为一个函数
function loadScript(url){
let script = document.createElement("script")
script.src = url
document.body.appendChild(script)
}
loadScript("client.js")
14.2.2 动态样式
CSS样式在HTML中可以通过两个元素加载
<link>包含外部CSS文件
<style>用于添加嵌入样式
抽象成通用函数
function loadStyle(url){
let link = document.createElement("link")
link.rel = "stylesheet"
link.type = "text/css"
link.href = url
let head = document.getElementsByTagName("head")[0]
head.appendChild(link)
}
loadStyle(url)
function loadStyleString(css){
let style = document.createElement("style")
style.type = "text/css"
try {
style.appendChild(document.createTextNode(css))
} catch(ex){
style.styleSheet.cssText = css
}
let head = document.getElementsByTagName("head")[0]
head.appendChild(style)
}
loadStyle(css)
14.2.3 操作表格
<table>
<tbody>
<tr>
p431
14.2.4 使用NodeList
NodeList,NamedNodeMap,HTMLCollection三个集合类型都是实时的(类似v-bind直接绑定)
NodeList就是对DOM的实时查询
14.3 MutationObserver接口
MutationObserver接口可以在DOM被修改时异步执行回调
使用MutationObserver可以观察整个文档/DOM树的一部分/某个元素
或者元素属性/子节点/文本的变化
创建实例
let observer = new MutationObserver(()=>console.log("DOM was nutated!"))
1.observe()方法
新创建的 MutationObserver对象需要通过observe()方法和DOM对象关联
接受两个参数:观察的DOM对象 MutationObserverInit对象
let observer = new MutationObserver(() => console.log('DOM was mutated!'))
observer.observe(document.body, { attributes: true })
document.body.className = 'foo'
console.log('changed body class');
回调的函数在异步队列,比同步的慢
2.回调与MutationRecord实例
每一个回调都会收到MutationRecord实例的数组
包含发生的变化以及受影响的DOM
let observer = new MutationObserver((mutationRecords) => console.log(mutationRecords))
observer.observe(document.body, { attributes: true })
document.body.className = 'foo'
console.log('changed body class');
连续修改会生成多个MutationRecord实例,下次执行回调就会收到半酣所有这些实例的数组
MutationRecord实例的属性:P434
3.disconnect()方法
一般来说,只要被观察的元素不被回收,MutationObserver的回调就会一直执行
要提前终止,可以调用disconnect()方法
4.复用MutationObserver
MutationObserver可以复用,以观察多个不同的目标节点
MutationRecord的target属性可以表示发生变化的目标节点
let observer = new MutationObserver((mutationRecords) => console.log(mutationRecords.map(x=>x.target)))
let div = document.createElement('div')
let span = document.createElement('span')
document.body.appendChild(div)
document.body.appendChild(span)
observer.observe(div, { attributes: true })
observer.observe(span, { attributes: true })
div.setAttribute('foo','bar')
span.setAttribute('foo','bar')
4.重用MutationObserver
调用disconnect()方法不会结束MutationObserver的生命,还可以重新使用这个观察者
14.3.2 MutationObserverInit与观察范围
MutationObserverInit的属性:P437
1.观察属性
MutationObserver可以观察属性的添加,移除和修改
但是要把attributes设置为true
要观察某个或某几个属性,需要用attributeFilter设置白名单
2.观察字符数据
观察文本节点的变化
需要把characterdata设置为true
3.其他变化
chlidList 观察子节点
对子节点重新排序会调用两次回调*设计删除和添加)
subtree 观察子树
14.3.3 异步回调与记录队列
1.记录队列
每次变化的信息都会记录在MutationRecord实例中,然后又添加到记录队列中
只有当MutationRecord被添加到MutationObserver的记录队列时没有已经排期的委任为回调,才会把注册的回调放到任务队列上,避免记录队列的内容被回调处理两次
2.takeRecords()方法
takeRecords()方法可以取出记录队列中的所有MutationRecord实例并清空队列
let observer = new MutationObserver((mutationRecords) => console.log(mutationRecords))
observer.observe(document.body, { attributes: true })
document.body.className = 'foo'
document.body.className = 'bar'
document.body.className = 'baz'
console.log(observer.takeRecords());
console.log(observer.takeRecords());
14.3.4 性能,内存与垃圾回收
MutationObserver与被观察节点的引用非对称
MutationObserver不会阻碍被观察节点的回收
被观察节点的回收却会导致MutationObserver的回收
每个MutationRecord至少包含一个对已有DOM节点的一个引用
建议提取最有用的信息后转存,然后就可以尽快释放内存