DOM 全称为 Document Object Model(文档对象模型)
其中,Document 特指 XML 或 HTML
Object 是指把文档变成对象
Model 文档和对象之间的映射关系叫做模型
DOM经常用来进行以下操作
- 获取元素
- 动态创建元素
- 对元素进行操作(设置其属性或调用其方法)
- 事件(什么时机做相应的操作)
DOM树(tree)
DOM又称为文档树模型
节点(Node)
JS中的对象继承自 Object
页面(document)中的对象继承自 Node(Node 是一个函数)
DOM 的最小组成单位就是 Node
节点(Node):网页中的所有内容都是节点,包含以下内容:
- 文档(Document):一个网页可以称为文档
- 元素(Element):网页中的标签
- 文本(Text):空格、回车、文字等内容
以及其他不重要的,例如属性(href)、注释(comment)等
document.body
typeof document.body // "object"
console.dir(document.body)
// 根据打印出的结果发现,document.body 的原型链为
// HTMLBodyElement <-- HTMLElement <-- Element <-- Node <-- EventTarget <-- Object
typeof document // "object"
console.dir(document)
// 根据打印出的结果发现,document.body 的原型链为
// HTMLDocument <-- Document <-- Node <-- EventTarget <-- Object
Document、Element、Text 的原型都指向 Node,最终 Node 的原型指向 Object
问:DOM 是如何操作节点的?
答:页面中的节点实际上是通过 Element、Text、Document、Comment 等构造函数构造出对应的对象,我们想操作哪个对象就调用其对应的API就可以了
Node 的接口
1. 属性
childNodes、firstChild、innerText、lastChild、nextSibling、nodeName、nodeType、nodeValue、outerText、ownerDocument、parentElement、parentNode、previousSibling、textContent
将以下单词组合就可以得到上面的属性
- child / children / parent
- node
- first / last
- next / previous
- sibling / siblings
- type
- value / text / content
- inner / outer
- element
关于 Node 属性,你需要注意以下几点:
- childNodes 和 children 的区别:
- childNodes返回的是节点的子节点集合,包括元素节点、文本节点(回车也属于文本节点)还有属性节点等
- children返回的只是节点的元素(标签)节点集合
- nodeName
- nodeType
节点类型常量
- ownerDocument 与 iframe 结合起来考虑
- innerText 与 textContent 的细微区别
- textContent 会获取所有元素的内容,包括 <script> 和 <style> 元素,然而 innerText 不会。
- 由于 innerText 受 CSS 样式的影响,它会触发重排(性能很低),但textContent 不会。
其他区别详见 textContent MDN
- nextSibling、previousSibling 可能会获取到文本
另外,如果你不知道该使用 innerText 还是 textContent,可采取以下写法:
'textContent' in document.body ? document.body.textContent : document.body.innerText
- childNodes 与 querySelectorAll
var parent = document.getElementById('parent');
parent.childNodes.length // 2
parent.appendChild(document.createElement('div'));
parent.childNodes.length // 请问现在 length 是多少?
答案是 3
var allDiv = document.querySelectorAll('div')
allDiv.length // 假设是 2
document.body.appendChild( document.createElement('div') )
allDiv.length // 请问现在 length 的值是多少?
答案是 2
问:为什么childNodes 的 length 会动态变化,而querySelectorAll 的 length 却不会动态变化?
答:
- parent.childNodes 是动态集合。所谓动态集合就是一个活的集合,DOM树删除或新增一个相关节点,都会立刻反映在NodeList接口之中。
- document.querySelectorAll方法返回的是一个静态集合。DOM内部的变化,并不会实时反映在该方法的返回结果之中。
2. 方法(如果一个属性是函数,那么这个属性就也叫做方法;换言之,方法是函数属性)
- appendChild()
- cloneNode()
- contains()
- hasChildNodes()
- insertBefore()
- isEqualNode()
- isSameNode()
- removeChild()
- replaceChild()
- normalize()
关于 Node 方法,你需要注意以下几点:
- cloneNode(true) 表示深克隆,cloneNode(false) 表示浅克隆
如果为 true,则该节点的所有后代节点也都会被克隆;如果为 false,则只克隆该节点本身。 - isEqualNode() 与 isSameNode() 的区别
- isEqualNode() 只是看起来相等
- isSameNode() 是真的相等(同一个),对于节点来说,isSameNode() 等价于 ===
- removeChild() 只是将子节点从页面中移除,使你看不见,但其实它依然存在于内存中。同样地,用 replaceChild() 将一个节点将另一个节点替换后,被替换的节点依然存在于内存中
- normalize() // 常规化
var wrapper = document.createElement("div");
wrapper.appendChild(document.createTextNode("Part 1 "));
wrapper.appendChild(document.createTextNode("Part 2 "));
// 这时(规范化之前),wrapper.childNodes.length === 2
// wrapper.childNodes[0].textContent === "Part 1 "
// wrapper.childNodes[1].textContent === "Part 2 "
wrapper.normalize();
// 现在(规范化之后), wrapper.childNodes.length === 1
// wrapper.childNodes[0].textContent === "Part 1 Part 2"
在标签里添加文本的方法:
<div id="div1">
<span>123</span>
</div>
① div1.innerText = 'hello'
// hello
此方法会直接覆盖 div 中原有的内容
② div1.appendChild(document.createTextNode('hello'))
// 123 hello
此方法不会覆盖 div 中原有的内容,只是在 div 中追加内容
Document 的接口
- 属性
anchors、body、characterSet、childElementCount、children、doctype、documentElement、domain、fullscreen、head、hidden、images、links、location、onxxxxxxxxx(事件监听)、origin、plugins、readyState、referrer、scripts、scrollingElement、styleSheets、title、visibilityState - 方法
- close()
- createDocumentFragment()
- createElement()
- createTextNode()
- execCommand() (当你想写一个富文本编辑器时使用)
- exitFullscreen()
- getElementById()
- getElementsByClassName()
- getElementsByName()
- getElementsByTagName()
- getSelection()
- hasFocus()
- open()
- querySelector()
- querySelectorAll()
- registerElement()
- write()
- writeln()
关于 Document 方法,你需要注意以下几点:
- querySelector() 与 querySelectorAll() 的区别
querySelector() 返回一个元素
querySelectorAll() 返回多个元素组成的伪数组(即使只有一个元素,依然会返回一个伪数组)
通过 DOM 的 API 获取到的 elements 都是伪数组
- close() 与 open()
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Document</title>
</head>
<body>
<script>
document.write(1)
document.write(2)
setTimeout(() => {
document.write(3)
}, 1000) // 会输出 1 2 3 吗?
</script>
</body>
</html>
答案是不会,输出结果是先打印出 1 2,然后打印出 3 并覆盖掉 1 2。
问:为什么不是像我们所期望的一样,先打印出 1 2,然后再打印出 1 2 3 呢?
答:Document 从页面第一个标签开始就会自动进入 open 状态,然后进入 write 状态,当<script></script>标签里的内容执行完毕后就进入 close 状态(close状态无法写入)。上述代码中设置了定时器,使document.write(3) 在 1 秒后才执行,1 秒后早已进入 close 状态,此时要写入 3 就必须重新进入 open 状态(相当于刷新了 Document),因此会覆盖掉 1 2
写 document.write() 时要注意不要出现在有延时性或者异步的操作中。
- innerText() 与 innerHTML() 的区别
http://js.jirengu.com/povuqolipi/1/edit?html,js,output
Element 的接口
总结
- DOM API 无外乎「增删改查」,大概知道如何对 div 进行增删改查即可
- 不需要死记硬背,用到的时候只需要查 MDN 即可