HTML中的DOM对象
DOM
DOM(文档对象模型)是针对 HTML 和 XML 文档的一个API。
- 主流浏览器都完成了 DOM1 级的实现
- IE 的 DOM 对象都是以 COM 对象的形式存在,和原始的 DOM 对象行为并不一致
一、节点层次
DOM 可以将 HTML 或 XML 文档描绘成一个由多层节点构成的结构,简单点说,就是一个树结构。
1.1 文档节点
文档节点是每个文档的根节点。
文档节点不直接表现在文档定义中,是一个内置的节点。
1.2 文档元素
文档元素是文档的最外层元素,文档中的所有其他元素都包含在文档元素中。
每个文档只能有一个文档元素。
在 HTML 页面中,文档元素始终是 <html>
元素。
1.3 节点结构
以 HTML 文档为例,结构如下:
1 | |-- Document |
其中,Document
是文档节点,它在 HTML 文档中是没有定义的;html
是文档元素,所有其他的元素都在它里面。
二、节点类型
2.1 Node 类型
Node 在 DOM1级定义中,是一个接口,由 DOM 中的所有节点类型实现。
在 JavaScript 中,Node 接口是作为 Node 类型实现的。
- JavaScript 中的所有节点类型都继承自 Node 类型
- 每个节点都有一个 nodeType 属性,用于表明节点的类型
- 除 IE 外,其他浏览器均可以访问 Node 类型(IE 没有公开它的 Node 类型的构造函数)
2.1.1 公共属性
(1) nodeType
每个节点都有一个 nodeType 属性,用于表明节点的类型。节点类型总共有12种(括号内的是它的常量值):
1 | - Node.ELEMENT_NODE(1) |
需要说明的是,并不是所有 Web 浏览器都支持全部的节点类型。
由于 IE 无法访问 Node 类型,因此在判断节点类型时,为了确保浏览器兼容性,最好将 nodeType 与数值作比较:
1 | // IE 下无效 |
(2) nodeName 和 nodeValue
这两个值取决于节点的类型,有可能取值为 null。
在 Node.ELEMENT_NODE
元素类型中,nodeName 始终保存的是元素的标签名,而 nodeValue 的值则始终是 null。
(3) childNodes
每个节点都有一个 childNodes 属性,用于保存它的子节点。
childNodes 是一个 NodeList 对象,是一种类数组对象(不是数组),其中保存着一组有序的节点。
NodeList 对象是一个动态查询的结果,也就是说,当 DOM 结构发生变化时,它能够实时更新数据。
1 | // 获取 NodeList 对象的长度 |
需要说明的是,虽然所有节点都继承自 Node 类型,但并不是每种节点都有子节点。
(4) 其他属性
- parentNode:父节点
- previousSibling:同级节点的前一个节点
- nextSibling:同级节点的下一个节点
- firstChild:第一个子节点
- lastChild:最后一个子节点
- ownerDocument:节点所在的文档
2.1.2 公共方法
(1) appendChild
appendChild()
方法用于向 childNodes 列表末尾追加一个节点。
如果追加的节点已经在 childNodes 中,则会将该节点移动到 childNodes 列表的末尾。
1 | // 追加到末尾 |
(2) insertBefore
insertBefore()
方法用于在 childNodes 列表的某个位置中插入一个节点。
1 | // 插入到末尾 |
(3) replaceChild
replaceChild()
方法用于将旧节点替换成新节点,实际上是做了一个删除节点操作和插入节点操作。
1 | // 替换指定的子节点 |
(4) removeChild
removeChild()
方法用于移除子节点。
1 | // 移除第一个子节点 |
(5) 其他方法
- cloneNode:用于创建调用节点的副本,
cloneNode(true)
为深复制,cloneNode(false)
为浅复制。 - normalize:用于处理后代的文本节点,后代中如果存在空的文本节点,则删除它;如果出现相邻的文本节点,则合并它们。
2.2 Document 类型
Document 类型用于表示文档,可以是 HTML 页面或者基于 XML 的文档。
- 在浏览器中,document 对象是 HTMLDocument(继承自 Document 类型)的一个实例,表示整个 HTML 页面
- document 对象是 window 对象的一个属性,可作全局对象访问
具有的特征包括:
- nodeName 的值为
#document
- nodeValue 的值为
null
- parentNode 的值为
null
- ownerDocument 的值为
null
(1) 文档子节点
DOM 标准规定 Documnet 节点的子节点可以是:
- DocumentType(最多一个)
- Element(最多一个)
- ProcessingInstruction
- Comment
document 对象拥有两个内置的访问其子节点的快捷方式,一个是 documentElement
,另一个是 body
。
document.documentElement
可以直接访问文档的<html>
元素(因为文档最多只会有一个 Element 元素)- 作为 HTMLDocument 的实例,
document.body
还可以直接访问文档的<body>
元素
其他子节点属性:
- DocumentType:
document.doctype
用来表示文档类型子节点,但是由于浏览器对document.doctype
的支持不一致,因此这个属性的用处不大 - Comment:理论上出现在
<html>
外面的注释,都属于 document 的子节点,但是不同浏览器对于这种注释的处理也存在差异,因此文档注释的意义也不大
(2) 文档信息
- title:
document.title
表示文档标题元素<title>
,修改document.title
的值会直接改变<title>
元素。 - URL:
document.URL
表示当前页面的完整 URL(即地址栏显示的 URL) - domain:
document.domain
表示页面的域名,与 URL 相关联 - referer:
document.refreer
表示来源页面的 URL(即跳转到当前页面的上一个页面 URL)
1 | // 修改页面标题 |
在 URL、domain、referer 中,只有 domain 可以修改,其他两个都是只读属性。
- domain 只能设置成 URL 的子域名,不能修改为 URL 中不包含的域
- 修改为子域名之后,不能再修改回原域名(即修改只能收缩,不能扩张)
1 | // http://www.baidu.com/ |
domain 的限制来源于跨域安全:来自不同子域的页面不能通过 Javascript 通信。也就是说,如果一个页面包含了一个内嵌框架,而且内嵌框架的域名和页面的域名不同,那它们之间就不能互相访问对方的 Javascript 对象。
1 | <!-- 假设当前页面来自 http://wwww.baidu.com/outer.html --> |
想要通过 Javascript 实现跨域访问,可以使用 window.postMessage()
方法。
(3) 特征检测
由于 DOM 分为多个级别,也包含多个部分,因此在使用某些特征功能之前,需要检测一下浏览器是否实现来该功能。
document.implemetation
属性提供了浏览器实现的相应信息和功能,其中 DOM1 级规定了一个方法 hasFeature()
用于检测 DOM 功能以及版本:
1 | var hasXmlDom = document.implementation.hasFeature('XML', '1.0') |
需要注意的是,hasFeature()
方法判断可能存在错误,因为功能是由浏览器负责实现的,但它未必完全按照 DOM 规范来实现,因此即使是在 hasFeature()
返回 true情况下,也未必能正确使用该功能,所以除了使用 hasFeature()
以外,应该还要加上能力检测。
(4) 查找元素
Document 类型查找元素的方法有,搜索的范围是整个文档:
- getElementById:通过元素的 ID 获取,区分大小写。如果文档中有多个相同 ID 的元素,则返回第一次出现的元素
- getElementsByTagName:通过元素的标签名获取,返回的是包含0个或多个元素的 NodeList
1 | // 按照 ID 获取 |
另外,对于 HTMLDocument 类型而言,还有另外的方法:
- getElementsByName:通过元素的name特性获取,返回的是包含0个或多个元素的 NodeList
getElementsByName()
方法常用在获取同一组单选按钮的值:
1 | // 按照name属性获取 |
另外,文档为常用的元素提供了快捷方式,可以免去我们自己查找:
document.anchors
:文档中所有带有 name 特性的<a>
元素document.links
:文档中所有带有 href 特性的<a>
元素document.applets
:文档中所有的<applet>
元素(不建议使用)document.forms
:文档中所有的<form>
元素document.images
:文档中所有的<img>
元素
(5) 文档写入
document 对象可以直接将输出流写入网页中,主要的方法有几个:
- write:写入字符串
- writeln:写入字符串,并在末尾添加一个换行符(
\n
) - open:打开输出流
- close:关闭输出流
需要注意的是,在文档加载期间使用 write()
和 writeln()
是不需要使用 open()
和 close()
方法的。
1 | <html> |
1 | <html> |
2.3 Element 类型
元素 Element 类型用于表示 XML 或 HTML 元素,具有的特征包括:
- nodeType 的值为 1
- nodeName 的值为元素的标签名
- nodeValue 的值为 null
- parentNode 可能是 Document 或 Element
访问元素的标签名可以使用 nodeName 或 tagName,在 Element 类型中这两个是相等的。
- 在 HTMl 中,标签名始终是全部大写表示
- 在 XML 中,标签名则始终和源代码中保持一致
因此在判断标签名时,最好转换一下大小写:
1 | if (element.tagName.toUpperCase() === 'DIV') { |
(1) HTML 元素
所有 HTML 元素都由 HTMLElement 类型表示,而 HTMLElement 继承自 Element,并添加了一些属性:
- id:元素的唯一标识符
- title:元素的附加说明信息,一般用于工具提示条显示
- lang:元素内容的语言代码,少用
- dir:语言的方向,少用
- className:元素的 class 特征,即元素的 CSS 类(class 是 ECMAScript 保留字,所以用 className 表示)
(2) 元素特性
特性是用于给出元素及其内容的附加信息。
1 | <!-- id、class、title、align、data-custom-attr 等称为元素特性 --> |
操作特性的 DOM 方法有几个:
- getAttribute:获取特性
- setAttribute:设置特性
- removeAttribute:删除特性
1 | var myDiv = document.getElementById('myDiv') |
(3) 元素属性
通过元素属性还可以访问元素特性,元素特性与元素属性的关系如下:
- 元素的特性可以通过 DOM 元素对象的属性访问
- 只有公认的(非自定义的)特性才会以属性的形式添加到 DOM 对象中(如 id、title 等)
- 通过属性获取得到的特性是经过解析的,是一个对象
1 | // element.id、element.className、element.title 等称为元素属性 |
有两类特性,它们虽然有对应属性名,但是属性值和通过 getAttribute()
拿到的特性值不同,属性值是经过解析的对象,而特性值是未经解析的字符串。这两类特性分别是样式 style,事件处理程序(onclick 等)。
1 | <div id="myDiv" style="height:100px;" onclick="btnClick"></div> |
1 | var myDiv = document.getElementById('myDiv') |
另外,Element 类型还有一个属性 attributes
用于保存元素所有的元素特性。element.attributes
属性包含一个 NamedNodeMap,是一个“动态”的集合,能够动态实时获取元素的所有特征。
(4) 创建元素
使用 document.createElement()
可以创建新元素,创建的同时,也为新元素设置了 ownerDocument
属性。
1 | var newDiv = document.createElement('div') |
(5) 查找元素
元素支持 getElementsByTagName()
方法,搜索起点为当前元素,范围是当前元素的子元素。
1 | // 查找指定元素下面的图片 |
2.4 Text 类型
文本节点由 Text 类型表示,包含的是纯文本内容,不能包含 HTML 代码,具有的特征:
- nodeType 的值为 3
- nodeName 的值为 “#text”
- nodeValue 的值为节点所包含的文本
- parentNode 是一个 Element
- 不支持(没有)子节点
文本节点的文本可以通过 nodeValue 或 data 属性获取,它们是相同的。
- 默认情况下,每个可以包含内容的元素最多只能有一个文本节点,而且必须确实有内容存在
- 修改文本时,字符串会经过 HTML (或 XML)编码(例如大于号、小于号等被转义成其他字符串)
- 如果两个文本节点是相邻的同胞节点,那它们的文本就会连起来显示,并且不会存在任何空格
1 | // 显示结果是 "<p>New text content</p>" |
(1) 创建文本节点
创建文本节点使用 document.createTextNode()
方法,方法接收文本作为参数,创建节点的同时,也会设置节点的 ownerDocument
属性。
1 | var newTextNode = document.createTextNode('Hello world!') |
需要注意的是,和设值一样,创建时的参数文本也会经过 HTML (或 XML)编码。
默认情况下,元素只有一个文本节点,但是通过 Javascript 可以为元素创建多个文本子节点:
1 | var myDiv = document.getElementById('myDiv') |
(2) 规范化文本
前面说过,如果两个节点是相邻的同胞节点,它们的文本会连起来显示,但是这样会导致分不清哪个字符串是哪个文本节点的,因此为了合并相邻的同胞文本节点,DOM 提供了一个方法 normalize()
,用于合并相邻的文本节点。
1 | var myDiv = document.getElementById('myDiv') |
(3) 分割文本
Text类型除了可以合并以外,还可以进行分割:splitText()
。这个方法会将1个文本节点分割成2个:
1 | var myDiv = document.getElementById('myDiv') |
2.5 Comment 类型
Comment 类型用于表示注释,与 Text 类型类似,具有特征如下:
- nodeType 的值为 8
- nodeName 的值为 “#comment”
- nodeValue 的值是注释的内容
- 不支持(没有)子节点
Comment 类型和 Text 类型继承自相同的基类,因此拥有除了 splitText()
方法以外的所有字符串操作方法。
创建注释节点可以通过使用 document.createComment()
,参数是注释内容:
1 | var comment = document.createComment('new comment') |
2.6 CDATASection 类型
CDATASection 类型只针对基于 XML 的文档,表示的是 CDATA 区域。具有的特征如下:
- nodeType 的值为4
- nodeName 的值为 “#cdata-section”
- nodeValue 的值是 CDATA 区域的内容
- 不支持(没有)子节点
与 Comment 类型类似,CDATASection 类型继承自 Text 类型,因此拥有除了 splitText()
方法以外的所有字符串操作方法。
CDATA 区域只会出现在 XML 文档中,因此浏览器一般会把 CDATA 区域错误解析为 Comment 或 Element。
在 XML 文档中创建 CDATA 区域,可以使用 document.createCDATASection()
方法,参数是区域的内容。
2.7 DocumentType 类型
DocumentType 类型包含着与文档的 doctype 有关的所有信息。具有的特征包括:
- nodeType 的值为 10
- nodeName 的值为 doctype 的名称
- nodeValue 的值为 null
- parentNode 为 Document
- 不支持(没有)子节点
在 DOM1 级中,DocumentType 对象不能动态创建,只能通过文档解析的方式创建。支持 DocumentType 的浏览器会将生成的 DoocumentType 对象保存在 document.doctype
中。
2.8 DocumentFragment 类型
DocumentFragment 类型表示文档片段,是一种“轻量级”的文档,可以包含和控制部分节点,但是不像完整文档那样占用额外的资源。在所有的节点类型中,只有 DocumentFragment 类型在文档中没有对应的标记。具有的特征包括:
- nodeType 的值为 11
- nodeName 的值为 “#document-fragment”
- nodeValue 的值为 null
- parentNode 的值为 null
文档片段不能直接添加到文档中,但是可以把文档片段中的节点添加到文档中。因此文档片段相当于一个节点临时仓库,可以将临时生成的节点保存在文档片段中,最后再将生成的节点都添加到文档中。
创建文档片段可以使用 document.createDocumentFragment()
方法:
1 | var fragment = document.createDocumentFragment() |
- 如果将文档中的节点添加到文档片段中,该节点就会从文档树中移除(也就是浏览器看不到了)
- 将文档片段的子节点添加到文档中,节点会从文档片段中移除,但是文档片段不会添加到文档树上
- 文档片段有类似于缓存的作用,可以用于避免浏览器反复渲染新信息
1 | var ul document.createElement('ul') |
2.9 Attr 类型
元素的特性在 DOM 中以 Attr 类型来表示。在所有浏览器中,都可以访问 Attr 类型的构造器和原型。从技术角度讲,特性就是存在于元素的 attributes
属性中的节点。具有的特征包括:
- ndoeType 的值为 2
- nodeName 的值就是特性的名称
- nodeValue 的值是特性的值
- parentNode 的值为 null
- 在 HTML 中不支持(没有)子节点
- 在 XML 中可以有 Text 或者 EntityReference 子节点
创建 Attr 节点可以使用 document.createAttribute()
方法,Attr 节点创建完成后,还需要使用 setAttribute()
方法来将属性值设到元素中:
1 | var myDiv = document.getElementById('myDiv') |
但是在开发中,直接使用 setAttribute()
方法会更加直接一些:
1 | var myDiv = document.getElementById('myDiv') |
HTML中的DOM对象