文章简介
DOM 是 JavaScript 操作网页的接口,全称为文档对象模型(Document Object Model)。DOM 操作是 JavaScript 最常见的任务,离开了 DOM,JavaScript 就无法操作网页。
本篇文章为【JavaScript 漫游】专栏的第 020 篇文章,对 DOM 规范的常用知识点进行了总结。
由于 DOM 规范的知识内容比较庞大,有不少知识点在日常开发工作中用得非常少,哪怕是在找工作的笔试题和面试题中都很少出现,所以这篇博客就只记录常用的知识点。
文章的内容顺序如下:
- DOM 概述,介绍 DOM、节点和节点树三者的基本概念,从而对 DOM 规范有初步的了解
- 接口总结,DOM 规范中定义了 Node、NodeList、HTMLCollection、ParentNode、ChildNode 这五大接口,这些接口提供的属性和方法有助于 JavaScript 操作 DOM 树,这里介绍每个接口的基本概念、常用属性和常用方法
- 节点总结,DOM 规范中还定义了 Document、Element、Text 和 DocumentFragment 这四种节点,它们提供的属性和方法同样有利于 JavaScript 操作 DOM 树,这里介绍 Document 和 Element 节点的基本概念、常用属性和常用方法(Text 和 DocumentFragment 节点不常用)。
- 属性操作总结,介绍 JavaScript 如何操作元素节点的属性。
- CSS操作简述,简单介绍 JavaScript 如何操作 CSS 的基本方法。
- Mutation Observer API 基本介绍,该 API 用于监视 DOM 的变动
DOM 概述
DOM 介绍
DOM 是 JavaScript 操作网页的接口,全称为文档对象模型(Document Object Model)。它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如增删查改)。
DOM 是一种接口规范,基于规范定义的 DOM 模型,能将结构化文档(比如 HTML)解析成一系列的节点,再由节点组成节点树(称为 DOMTree)。所有的节点和最终的树状结构,都有规范的对外接口。任何一种编程语言,只要它实现了这种接口规范,它就具备能够对结构化文档的内容进行增删查改等操作的能力。JavaScript 正是实现了 DOM 规范,从而具备了在浏览器上操作 HTML 文档内容的能力。
节点和节点树
DOM 的最小组成单位叫做节点(node)。各种不同类型的节点,可以组成节点树。
节点的类型有七种。
Document
:整个文档树的顶层节点DocumentType
:doctype
标签Element
:网页的各种 HTML 标签(比如<body>
)Attr
:网页元素的属性(比如class="right"
)Text
:标签之间或标签包含的文本Comment
:注释DocumentFragment
:文档的片段
这些节点都具备公共或独有的属性和方法,以供 JavaScript 使用。
DOM 树的存在,主要是提供一种上下文,JavaScript 可以先拿到某一个节点,再去找它的祖父节点、兄弟节点和子孙节点等,这样就可以对整个文档的所有节点都进行操作。
DOM 接口总结
Node 接口
所有 DOM 节点对象都继承了 Node 接口,拥有一些共同的属性和方法。这是 DOM 操作的基础。
它的实例属性中常用的有:
textContent
baseURI
previousSibling
parentNode
parentElement
firstChild
和lastChild
childNodes
常用的实例方法有:
appendChild()
insertBefore()
removeChild()
replaceChild()
textContent
需要获取当前节点和它的所有后代节点的文本内容时,就用这个属性。它会自动忽略当前节点内部的 HTML 标签,返回所有文本内容。
// HTML 代码为
// <div id="divA">This is <span>some</span> text</div>
document.getElementById('divA').textContent;
// This is some text
这个属性是可读写的,通过设置属性的值,新的文本节点就会替换原来的所有子节点。它还有一个用处,就是用于对 HTML 标签转义。
document.getElementById('foo').textContent = '<p>GoodBye!</p>';
上面代码在插入文本时,会将 <p>
标签解释为文本,而不会当作标签去处理。
baseURI
要获取当前网页的绝对路径时,就用这个属性。记住它是只读的。
// 当前网页的网址为
// http://www.example.com/index.html
document.baseURI
// "http://www.example.com/index.html"
如果读不到,就返回 null
。
属性值一般是当前网址的 URL 决定,但是可以使用 HTML 的 <base>
标签来改变它的值。
// <base href="http://www.example.com/page.html">
document.baseURI
// "http://www.example.com/page.html"
nextSibling
和 previousSibling
通过前者可以获取当前节点毗邻的下一个兄弟节点,后者则可以获取到当前节点毗邻的前一个兄弟节点。
要注意的是,属性值包括了文本节点和注释节点,如果当前节点前后有空格,那么它们的属性值就是空格对应的文本节点。
// HTML 代码如下
// <div id="d1">hello</div><div id="d2">world</div>
var d1 = document.getElementById('d1');
var d2 = document.getElementById('d2');
d1.nextSibling === d2 // true
d2.previousSibling === d1 // true
parentNode
和 parentElement
前者拿到父节点,后者拿到父元素,区别就在于拿到的父节点可以为三种节点类型:元素节点(element
)、文档节点(document
)和文档片段节点(documentfragment
)。后者相当于把文档节点和文档片段节点都排除掉了。
if (node.parentNode) {
node.parentNode.removeChild(node);
}
if (node.parentElement) {
node.parentElement.style.color = 'red';
}
firstChild
和 lastChild
前者获取第一个子节点,后者获取最后一个子节点。
// HTML 代码如下
// <p id="p1"><span>First span</span></p>
var p1 = document.getElementById('p1');
p1.firstChild.nodeName // "SPAN"
childNodes
要获取当前节点的所有子节点时,就用这个属性。它会返回一个类数组对象,NodeList
集合,成员包含当前节点的所有子节点。
var div = document.getElementById('div1');
var children = div.childNodes;
for (var i = 0; i < children.length; i++) {
// ...
}
注意 NodeList
对象是一个动态集合,一旦子节点发生变化,立刻会反映在返回结果之中。
appendChild()
给当前节点增加一个子节点,位置在末尾。参数是一个节点对象,返回值也是它。
var p = document.createElement('p');
document.body.appendChild(p);
如果参数节点是 DOM 中已经存在的节点,方法会将其从原来的位置,移动到新位置。
var div = document.getElementById('myDiv');
document.body.appendChild(div);
还有,如果参数是 DocumentFragment
节点,那么插入的是 DocumentFragment
的所有子节点,而不是 DocumentFragment
节点本身。返回值是一个空的 DocumentFragment
节点。
insertBefore()
需要将某个节点插入到父节点内部的指定位置时,就需要用到这个方法。语法如下:
var insertedNode = parentNode.insertBefore(newNode, referenceNode);
newNode
:要插