本篇是基于《JavaScript高级程序设计(第3版)》DOM相关章节做的整理与归纳,概述了DOM的常见节点类型及核心要点。本篇适合初级JS开发者阅读以提升对DOM的理解与运用。
0. Node —— DOM基础节点
- JavaScript中的所有节点类型都继承自
Node
类型,因此所有节点类型都共享着相同的基本属性和方法。 - 每个节点都有一个
nodeType
属性,用于表明节点的类型。
if(someNode.nodeType === 1){
alert('Node is an element'); //该节点为元素节点
}
- 规范中共有12种节点类型(元素节点、属性节点、文本节点、注释、预处理命令。。。),但受到的支持因浏览器而异。当然,最常用的元素节点、属性节点、文本节点等自然是受到广泛且较为统一的支持的。
- 节点之间的关系:
- 每个节点都有一个
childNodes
属性,其中保存着其所有子节点(并非每种节点都有子节点,比如文本节点)。这些子节点用一个NodeList
对象表示,这是一个类数组对象,可通过方括号索引或者.item()
方法取值,且具有length
属性。需要指出的是,NodeList
对象是动态的,它会随着实际DOM的变化而时刻变化。 - 每个节点都有一个
parentNode
属性,指向其父节点。 -
someNode.firstChild
、someNode.lastChild
—— 指向第一个子节点、最后一个子节点。 -
someNode.nextSibling
、someNode.previousSibling
—— 指向下一个兄弟节点、上一个兄弟节点。 - 通过
someNode.hasChildNodes()
或someNode.childNodes.length
判断是否有子节点。 - 任何节点都不可能同时存在于多个文档中,
someNode.ownerDocument
指向拥有该节点的文档,即document
。 - 节点的操作:
-
appendChild()
:var node = someNode.appendChild(newNode)
—— 添加一个节点到childNodes
的最后,并返回该节点。
注意:如果newNode
已经存在于文档中,则这句语句的效果变成移动该节点。 -
insertBefore()
:var node = someNode.insertBefore(newNode, someNode.firstChild)
—— 在someNode.firstChild
之前插入一个新节点,并返回插入的节点。 -
replaceChild()
:var node = someNode.replaceChild(newNode, someNode.firstChild)
—— 用newNode
替换掉someNode.firstChild
,返回newNode
。 -
removeChild()
:var node = someNode.removeChild(someNode.lastChild)
—— 移除最后一个节点,返回被移除的节点。 -
cloneNode()
:复制节点(树)的副本。复制出的副本不会指定其父节点,需要手动将其添加到文档中。还要注意,这个方法不会复制onclick=""
等JavaScript属性。-
someNode.cloneNode(true)
—— 深复制(复制节点及其子节点树); -
someNode.cloneNode(false)
—— 浅复制(仅复制节点本身);
-
1. Document类型 —— 文档节点
- 在浏览器中,
document
对象是HTMLDocument
类型的一个实例,而HTMLDocument
继承自Document
类型。 -
docuemnt
对象作为文档的顶层节点,我们称其为文档节点。 - 文档节点的
nodeType
等于9
。 - 文档节点的
nodeName
等于'#document'
。 -
document
节点一般拥有如下子节点: -
DocumentType
文档类型节点 —— 即文档顶部的文档类型声明标签。如:<!DOCTYPE HTML>
-
Element
元素节点 —— 在浏览器中,即<html>...</html>
-
Comment
注释节点 —— 即注释(出现在<html>...</html>
外部的注释)。 -
document
子节点的访问: -
document.doctype
—— 访问文档类型声明标签。 -
document.documentElement
—— 访问文档中的<html>...</html>
元素。 - 扩展:
document.body
直接指向html
中的body
元素。 -
document
文档信息: -
document.title
—— 文档(页面)标题。 -
document.URL
—— 文档(页面)完整的URL,只读。一般情况下与window.location.href
相同。 -
document.domain
—— 文档(页面)的域名。 -
document.refererer
—— 链接到当前文档(页面)的那个页面的URL。 -
document
查找元素(HTMLDocument): -
document.getElementById('myId')
:获取文档中id
属性值为myId
的元素。 -
document.getElementsByTagName('ul')
:获取文档中所有的无序列表。返回结果是一个NodeList
,不过在HTML中,实际返回的是一个HTMLCollection
对象。(HTMLCollection
表示元素集合,而NodeList
表示节点集合。) -
document.getElementsByName('myName')
:获取文档中所有name
属性值为myName
的元素。返回结果同样是一个HTMLCollection
对象。 -
document
特殊集合:
这些集合都是HTMLCollection
对象,对一些常用元素的访问提供快捷方式。-
document.anchors
—— 获取文档中所有带name
属性的<a></a>
元素。 -
document.links
—— 获取文档中所有带href
属性的<a></a>
元素。 -
document.images
—— 获取文档中所有的<img />
元素。
-
-
document.write()
:文档写入
可以使用该方法动态地引入外部资源,例如引入一个JS文件。
2. Element类型 —— 元素节点
- 元素节点的
nodeType
等于1
。 - 元素节点的
nodeName
等于元素的标签名(大写。与tagName
属性返回的值一致)。 - 所有HTML元素都由
HTMLElement
或其子类型表示,HTMLElement
类型继承自Element
类型。 -
HTMLElement
类型设定了id
、className
、title
等基本属性,因而所有HTML元素都支持这些属性。 - 不同元素所支持的属性有所不同,原因就在于这些元素由更具体的子类型来表示和实现,而非全部采用
HTMLElement
类型,比如<button></button>
元素由HTMLButtonElement
来表示。不过,HTMLElement
提供的公共属性对任何元素而言都是不会变的。 - 对于任何元素的任何属性(包括自定义属性),我们都可以通过
getAttribute()
、setAttribute()
、removeAttribute()
这三个DOM方法进行属性访问、属性设置(新增+更改)、属性删除的操作。 - 元素节点还拥有一个
attributes
属性,其值是一个NamedNodeMap
对象,类似NodeList
。这个attributes
包含了某个HTML元素所有的属性信息。 - 使用
document.createElement()
可以创建新元素。如:
var newDiv = document.createElement('div');
var newDivIE = document.createElement('<div id=\'myNewDiv\' class=\'box\'></div>'); //除了上面的规范方法,IE还支持这种方式创建新元素
新创建的元素需要使用appendChild()
、insertBefore()
等方法手动添加到DOM树中。
- 元素的子节点,即
someElementNode.childNodes
中保存着该元素所有的子节点,这些子节点可能是元素、文本、注释等,在实际操作前最好通过nodeType
属性进行检查。
3. Text类型 —— 文本节点
- 文本节点的
nodeType
等于3
. - 文本节点的
nodeName
等于'#text'
。 - 文本节点的
nodeValue
即为包含的文本内容。 - 文本节点的父节点是元素节点,没有子节点。
- 文本节点拥有一个
length
属性,表示该文本中的字符数。 - 空格也会生成文本节点。
- 文本节点的创建:
document.createTextNode('文本内容')
。 - 浏览器在解析文档时永远不会创建相邻的文本节点,也就是说默认情况下,一个元素节点下最多只能有一个文本节点。
- 在手动执行某些DOM操作的时候下,会出现多个互为同胞的文本节点,这容易导致混乱。
- 规范化文本节点:
element.normalize()
。作用就是将某个元素内的所有相邻文本节点合并为同一个文本节点。normalize()
方法由Node
类型实现和提供,因此可全局使用。 - 分割文本节点:
var txt = textNode.splitText(3)
。分割文本节点是从文本节点中提取数据的一种常用DOM解析技术。
4. Comment类型 —— 注释节点
- 注释节点的
nodeType
等于8
。 - 注释节点的
nodeName
等于'#comment'
。 - 注释节点的
nodeValue
即为注释的内容。 - 注释节点没有子节点。
- 注释节点的创建:
document.createComment('注释内容')
。
5. DocumentType类型 —— 文档类型声明节点
- 文档类型声明节点即文档最顶部的类型声明,比如HTML5文档顶部的
<!DOCTYPE HTML>
。 - 文档类型声明节点的
nodeType
等于10
。 - 文档类型声明节点的
nodeName
等于文档的类型名。 - 显然, 文档类型声明节点没有子节点。
- 浏览器解析文档后,会把该节点对象保存在
document.doctype
中,因此可通过document.doctype.name
直接取得当前文档的类型。
6. DocumentFragment类型 —— 文档片段节点
- 文档片段节点在文档中没有对应的标记。其作用就是作为一个“仓库”,它可以包含和控制节点(文档片段),但不占用额外的资源,在需要时可将这个“仓库”中的内容添加到文档中。
- 文档片段节点的
nodeType
等于11
。 - 文档片段节点的
nodeName
等于'#document-fragment'
。 - 文档片段节点没有父节点,因为它只能通过代码创建出来,且始终独立于文档。
- 文档片段节点无法直接被添加到文档中,我们只能将该节点内部的“文档片段”添加到DOM中。
- 文档片段节点的创建与使用:
<!-- 假设页面中有一个空的列表,需要动态添加数据 -->
<ul id="myList"></ul>
var fragment = document.createDocumentFragment();
var list = document.getElementById('myList');
var li = null;
for(var i=0; i < 3; i++){
li = document.createElement('li');
li.appendChild(document.createTextNode('Item' + (i + 1)));
fragment.appendChild(li);
}
list.appendChild(fragment);
- 注意,在执行了
list.appendChild(fragment)
后,fragment
内部的所有子节点都会被删除并转移到list
中,但文档片段节点本身 ——fragment
仍然存在,这相当于执行了一次剪切操作。
7. Attr类型 —— 属性节点
- 尽管就技术上而言,属性节点也是节点,但我们一般不将其视作DOM树的一部分。
- 从HTML的角度来看,属性即元素标签内的特性;从JS的角度看,属性就是元素节点对象实现和提供的相关节点。
- 属性节点的
nodeType
等于2
. - 属性节点的
nodeName
等于属性名。 - 属性节点的
nodeValue
等于属性值。 - 属性节点在HTML中没有子节点。
- 属性对象有一个布尔型属性
specified
用以表示该属性是默认的还是代码指定的。 - 属性节点的获取、创建与使用:
element.getAttributeNode('id')//获取属性节点
、var attr = document.createAttribute('name')//创建属性节点
、element.setAttributeNode(attr)//使用属性节点
。 - 上面都是对纯粹的属性节点的相关介绍,在实际开发中,对于属性的操作我们一般都使用
getAttribute()
、setAttribute()
、removeAttribute()
这几个更加好用的方法。