从这篇笔记开始整理JavaScript的第三部分:文档对象模型DOM(Document Object Model)。DOM是针对HTML和XML文档的一个API,脱胎于DHTML,由W3C负责制定相关标准,现在已经成为表现和操作页面标记的真正的跨平台、语言中立的一种标准,除了JavaScript外,其它一些语言比如SVG、MathML等也不同程度的实现了各自的DOM。
1、DOM组成和级别
DOM分为三个组成部分和三个级别:
组成部分 | 说明 |
核心DOM | 用于任何结构化文档的标准模型 |
XML DOM | 用于XML文档的标准模型,定义了所有XML元素的对象和属性,以及访问它们的方法(接口),换句话说,XML DOM是用于获取、更改、添加或删除XML元素的标准 |
HMTL DOM | 用于HTML文档的标准模型,定义了所有HTML元素的对象和属性,以及访问它们的方法(接口) |
DOM级别 | 浏览器支持情况 | 功能模块 | 说明 |
0级 | IE4、Netscape4 | 在W3C标准中,是没有0级的,通常所谓DOM 0级指的就是在DOM 1级规范之前的在IE4和Netscape Navigator4中支持的DHTML | |
1级 | 几乎所有现代浏览器 | DOM核心(DOM Core) | 规定的是如何映射基于XML的文档结构,以便简化对文档中任意部分的访问和操作 |
DOM HTML | 在DOM核心基础上加以扩展,添加了针对HTML的对象和方法 | ||
2级 | IE 9+ Opera 7-9.9(部分支持),OPera 10+ Safari 2+(部分支持) Chrome 1+(部分支持) Firefox 1+(几乎全部) |
DOM视图(DOM Views) | 定义了跟踪不同文档(例如,应用CSS前后的文档)视图的接口 |
DOM事件(DOM Events) | 定义了事件和事件处理的接口 | ||
DOM样式(DOM Style) | 定义了基于CSS为元素应用样式的接口 | ||
DOM遍历和范围(DOM Traversal and Range) | 定义了遍历和操作文档树的接口 | ||
DOM核心和HTML扩展 | 开始支持XML命名空间等 | ||
3级 | IE9+ Opera9+(部分支持) Firefox1+(部分支持) |
DOM加载和保存(DOM Load and Save) | 引入了以统一方式加载和保存文档的方法 |
DOM验证(DOM Validation) | 验证文档的方法 | ||
DOM核心和HTML扩展 | 开始支持XML1.0规范,涉及XML Infoset、XPath和XMLBase等 |
2、文档映射
DOM将HTML和XML文档映射成一个由不同节点组成的树型机构,每种节点都对应于文档中的信息或标记,节点有自己的属性和方法,并和其他节点存在某种关系,节点之间的关系构成了节点层次,例如:
<html> <head> <title>标题</title> </head> <body> <p>测试</p> </body> </html> (1)文档节点Document是每个文档的根节点。 |
3、Node接口
类别 | 属性/方法 | 值/类型/返回类型 | 说明 |
属性 | nodeName | String | 节点名字,根据节点类型定义。对于元素节点,就是原始的标签名 |
nodeValue | String | 节点值,根据节点类型定义。对于元素节点为null | |
nodeType | Number | 节点类型,返回12种节点类型值之一 | |
ownerDocument | Document | 指向这个节点所属的文档,可以利用它直接访问文档节点,而不用层层回溯 | |
parentNode | Node | 文档树中的父节点 | |
childNodes | NodeList | 所有直接子节点组成的列表,不同浏览器对空白字符和<html>外的注释有不同处理,会导致childNodes不一致 | |
firstChild | Node | 第一个直接子节点,没有子节点返回null | |
lastChild | Node | 最后一个直接子节点,没有子节点返回null | |
previousSibiling | Node | 前一个兄弟节点,没有则返回null | |
nextSibiling | Node | 后一个兄弟节点,没有则返回null | |
方法 | hasChildNodes() | Boolean | 有子节点时返回true,否则返回false |
appendChild(node) | Node | 在末尾添加子节点,返回新添加的节点。如果传入参数已经是文档中一部分,结果将是将该节点从原来的位置移向新位置(任何DOM节点不能同时出现在多个位置上) | |
removeChild(node) | Node | 移除节点并返回这个节点。删除后,节点仍然属于原来的文档,只是没有了位置 | |
replaceChild(node,node) | Node | 传入新添加的节点和被替换的节点,返回被替换的节点。被替换的节点仍然属于原来的文档,只是没有了位置 | |
insertBefore(node,node) | Node | 传入新添加的节点和参照节点,新添加节点会成为参照节点的前一个兄弟节点,如果参照节点为null,则插入到最后,相当于appendChild()。返回新添加的节点 | |
cloneNode(boolean) | Node | 复制节点,参数为true时,复制节点及其所有子节点树,为false时,只复制节点本身。返回的节点属于文档所有,但没有指定父节点 | |
normalize() | 处理文档树中的文本节点,删除空文档节点或合并两个相邻的文本节点等 |
说明:
(1)关于节点类型nodeType,在DOM1中定义了12种常量,是作为Node类型构造函数的属性定义的(静态属性),它们对应于各自的节点类型:
节点 | 节点类型(nodeType,Node的静态属性) | 节点名称(nodeName) | 节点值(nodeValue) | 父节点(parentNode) | 子节点(childNodes) | 说明 |
Document | Node.DOCUMENT_NODE(9) | #document | null | null | DocumentType(最多一个)|Element(最多一个) |Comment|ProcessingInstruction |
下有进一步叙述 |
Element | Node.ELEMENT_NODE(1) | 元素的标签名 | null | Document|Element | Element|Text|Comment| ProcessingInstruction| CDATASection|EntityReference |
|
Text | Node.TEXT_NODE(3) | #text | 节点所包含的文本 | Element | (不支持)没有子节点 | |
Comment | Node.COMMENT_NODE(8) | #comment | 注释的内容 | Document|Element | (不支持)没有子节点 | 和Text继承自相同基类,拥有除splitText()外所有字符串方法,可通过nodeValue或data属性获取注释内容 |
CDATASection | Node.CDATA_SECTION_NODE(4) | #cdata-section | CDATA区域的内容 | Document|Element | (不支持)没有子节点 | 继承自Text类型,拥有除splitText()外所有字符串方法 |
DocumentType | Node.DOCUMENT_TYPE_NODE(10) | doctype的名称 | null | Document | (不支持)没有子节点 | 在DOM1中不能动态创建,DocumentType对象的3个属性: name表示文档类型名称 entities表示描述文档类型实体的NamedNodeMap notations表示文档类型描述的符号的NamedNodeMap |
DocumentFragment | Node.DOCUMENT_FRAGMENT_NODE(11) | #document-fragment | null | null | 同Element类型 | 下有进一步叙述 |
Attr | Node.ATTRIBUTE_NODE(2) | 特性名称 | 特性值 | null | HTML中不支持,XML中可以是Text或EntityReference | 有3个自己的属性: name等于nodeName value等于nodeValue specified表示是否为默认设置 |
EntityReference | Node.ENTITY_REFERENCE_NODE(5) | 引用的实体名称 | null |
实体引用节点 | ||
Entity | Node.ENTITY_NODE(6) | entity name | null |
实体节点 | ||
ProcessingInstruction | Node.PROCESSING_INSTRUCTION_NODE(7) | 与 ProcessingInstruction.target 相同 |
与 ProcessingInstruction.data 相同 |
处理指令 | ||
Notation | Node.NOTATION_NODE(12) | notation name | null |
DTD中定义的符号 |
这些节点类型都实现了Node接口,因此都可以访问Node类型的属性和方法(不支持子节点的节点类型上调用appendChild()、insertBefore()、replaceChild()、removeChild()等方法时会抛出异常)。经过测试在IE9中已经可以直接访问Node类型中定义的常量值,并且IE9中这些值不能改变,而在Firefox15的版本中仍然可以修改,这应该是Firefox实现的一个Bug(原书说IE中不可访问Node的论述似有不妥)。
Node.DOCUMENT_NODE = 2; console.info(Node.DOCUMENT_NODE);//IE9输出9,而Firefox15输出的是2
(2)关于NodeList类型,它也是一个类数组类型,有length属性,也可以通过方括号语法访问,还可以通过item()方法访问,但并不是Array的实例,如果要对其使用数组方法,必须像转换arguments对象一样来转换NodeList对象:
var node = document;//任意一个节点,这里使用文档根节点测试 console.info(node.childNodes.length);//2,直接子节点 console.info(node.childNodes[0].nodeName);//通过方括号语法访问第1个元素,索引从0开始 console.info(node.childNodes.item(1).nodeName);//通过item()方法访问第2个元素,索引从0开始 var arr = Array.prototype.slice.call(node.childNodes,0);//转换为真正的数组 console.info(arr.join(','));//可以使用数组方法了
需要特别注意的是,NodeList对象类型是一个有生命、有呼吸的对象,它的属性和元素是跟随文档变化而变化的:
var node = document; var nodeList = node.childNodes; var src = nodeList.length; node.removeChild(nodeList[0]);//修改文档,会同时修改NodeList对象的属性和元素,即便是已经将其保存为另外一个变量 console.info(nodeList.length == src - 1);//true
类似NodeList这种动态变化的对象对象还有HTMLCollection、NamedNodeMap等,其中HTMLCollection还有一个namedItem()方法,可以通过元素的name获取集合中的项。
4、Document类型
在JavaScript中,通过Document类型表示文档,而我们通常使用的document对象则是HTMLDocument(继承自Document类型)的一个实例,表示整个HTML页面。同时,docuemnt对象也是window对象的一个属性,可以作为全局对象来访问。document对象的主要属性和方法有:
类别 | 属性/方法 | 说明 | 备注 |
属性 | documentElement | 指向HTML页面中的<html>元素 | 作为document的子节点,<html>元素还可以通过document.firstChild或document.childNodes[0]等方式访问 |
body | 直接指向HTML页面中的<body>元素 | document.body的使用频率非常高 | |
doctype | 表示<!DOCTYPE>相关信息,不同浏览器差异比较大,因此用处不大 | 有些浏览器会把<!DOCTYPE>作为注释处理 | |
title | 包含<title>元素中的文本,显示在浏览器窗口的标题和标签页上,可以通过它修改标题 | 修改title属性的值不会改变<title>元素 | |
URL | 页面完整的URL,即地址栏中显示的URL | 这些信息都存在于请求的HTTP头部,这些属性是为了方便在JavaScript中访问,domain的设置需要注意: 1、不能将domain设置为URL不包含的域,如URL='p2p.wrox.com',domain可以设置为wrox.com,但不能设置为ncz.net 2、不能将domain由松散的设置为紧绷的,如原来domain='wrox.com',不能设置为domain='p2p.wrox.com' |
|
domain | 页面的域名,可以设置 | ||
referrer | 链接到当前页面的那个页面的URL,没有来源页面时,为空字符串 | ||
implementation | 提供浏览器对DOM实现情况的描述对象,在DOM1只有一个hasFeature()方法 | hasFeature()接受两个参数:DOM功能和版本号。由于浏览器实现问题,这个方法并非百分百准确 | |
文档元素集合 | anchors | 包含文档中所有带name特性的<a>元素 | 这些集合都是HTMLCollection对象,集合中的项会随文档的变化而更新 |
applets | 包含文档中所有<applet>元素,已经不推荐使用 | ||
forms | 包含文档中所有<form>元素,相当于document.getElementsByTagName('form') | ||
images | 包含文档中所有<img>元素,相当于document.getElementsByTagName('img') | ||
links | 包含文档中所有带href特性的<a>元素 | ||
元素获取方法 | getElementById() | 接受一个参数:要获取元素的ID,匹配大小写,找不到时返回null,若有多个相同ID元素,返回第一个 | 在IE8-中不区分大小写,并且表单元素(如<input>)中的name也被作为ID去查询 |
getElementsByTagName() | 接受一个参数:要获取元素的标签名,返回包含0或多个元素的NodeList,在HTML中,返回HTMLCollection | 传入*号时,则返回文档中所有元素,可以通过索引(调用item())、字符串(调用namedItem())、以及直接使用item()方法访问结果集 | |
getElementsByName() | HTMLDocument特有方法,返回name特性为传入参数值的元素集合,返回HTMLCollection对象 | 使用namedItem()时,只返回第一项,因为是按name获取的集合,集合中的name全部相同 | |
文档写入方法 | write() | 输出文本,接受一个参数:写入到输出流的文本 | write()和writeln()被用来向页面动态地输入内容,需要注意的是: 1、如果输出内容中含'</script>',为了防止做为标签解析而发生错误,需要特殊处理,可以写成'<\/script>'或拆分 2、如果是页面加载完成之后调用,会重写整个页面 |
writeln() | 输出文本,并添加换行符(\n),接受一个参数:写入到输出流的文本 | ||
open() | 打开网页输出流 | ||
close() | 关闭网页输出流 | ||
创建方法 | createElement() | 创建新元素,并设置ownerDocument属性,接受一个参数:要创建元素的标签名(在HTML中不分大小写,在XML中区分) | 在IE中还可以传入包含属性的完整的元素标签来创建新元素 |
createTextNode() | 创建文本节点,接受一个参数:要插入节点中的文本(会按HTML/XML格式编码),会同时设置ownerDocument | 除非把新节点添加到文档中,否则不会显示新节点 | |
createComment() | 创建新注释,接受注释文本 | 浏览器不会识别<html>后代注释,如要访问注释节点,需要保证是<html>元素的后代 | |
createCDATASection() | 在XML文件中创建CDATA区域,传入节点内容 | CDATA区域只会出现XML文档中,因此多数浏览器会报CDATA区域错误地解析为Comment或Element | |
createDocumentFragment() | 创建文档片段 | ||
createAttribute() | 创建新特性节点 |
说明:
(1)一般情况下,不用在document对象上调用appendChild()、insertBefore()、removeChild()、replaceChild()等方法,因为文档类型(如果存在的话)是只读的,而且它只能有一个元素子节点。
(2)document对象的创建方法,是实现动态加载脚本或样式的基础,从而可以进一步对代码模块化,实现按需加载,提升性能,这在Ext4库中已经很好地应用了。动态加载的一般方法:
function loadScript(url){//动态加载外部js文件,也可以类似的加载js代码 var script = document.createElement("script"); script.type = "text/javascript"; script.src = url; document.body.appendChild(script); } function loadStyle(url){//动态加载外部css文件,也可以类似的通过style元素加载css代码 var link = document.createElement("link"); link.rel = "stylesheet"; link.type = "text/css"; link.href = url; var head = document.getElementsByTagName("head")[0]; head.appendChild(link); }
5、Element类型
Element类型用于表现XML或HTML元素,提供了对元素标签名、子节点以及特性的访问。在HTML中,元素由HTMLElement类型(或其子类型)表示,HTMLElement继承了Element类型,并添加了对应每个HTML元素都存在的标准特性的属性。HTMLElement类型常用属性和方法总结如下:
类别 | 属性/特性 | 说明 | |
Element类型属性 | tagName | 这个属性是所有Element都有的,值和nodeName相同,表示元素标签名,在HTML中,返回的标签名始终大写,在XML中,和源代码一致 | |
HTML元素标准特性 | id | id | 元素在文档中的唯一标识符 |
title | title | 有关元素的附件说明信息,一般通过工具条显示出来 | |
lang | lang | 元素内容的语言代码,很少使用 | |
dir | dir | 语言的方向,ltr:从左至右,rtl:从右至左,也很少使用 | |
className | class | 元素的CSS类,因为class是保留字,所以属性命名为className | |
特性属性 | attributes | 这个是属性只有Element类型使用,是一个NamedNodeMap值,属于一个“动态”集合 | |
特性方法 | getAttribute() | 参数:特性名。参数必须与实际的特性名相同,因此是class而不是className,给定特性不存在时,返回null,这个方法也可以获取自定义特性,特性名不区分大小写 | |
setAttribute() | 参数:特性名和特性值。如果已存在该特性,替换现有的值,如果不存在,就创建并赋值。通过这个方法设置特性时,会把特性名转换为小写形式 | ||
removeAttribute() | 参数:特性名。彻底删除元素的特性,不仅会清除特性的值,也会从元素对象中删除特性 | ||
特性节点方法 | getAttributeNode() | 返回对应特性的Attr节点,element.getAttributeNode('align')相当于element.attributes['align'] | |
setAttributeNode() | 设置元素的Attr节点 | ||
其它方法 | getElementsByTagName() | 在当前元素的后代节点中搜索,其它用法和Document类型的同名方法一样 |
说明:
(1)特殊特性:
A、class特性,其对应DOM对象的属性为className,在特性方法操作中参数需要传入class,而对象属性操作需要设置className属性。
B、style特性,在通过getAttribute()访问时,返回的是style特性值中包含的CSS文本,而通过属性来访问它则会返回一个对象。
C、事件处理特性,比如onclick,当在元素上使用时,onclick特性中包含的是JavaScript代码,如果通过getAttribute()访问,返回相应代码的字符串,而在访问onclick属性时,会返回一个JavaScript函数(没有相应特性时返回null)。
(2)关于自定义特性和属性,在HTML5规范中,需加上“data-”前缀。
A、给节点设置特性值时,会同步修改相应DOM对象的属性值,但是设置自定义特性的值时,一般浏览器是不会为相应的DOM对象添加属性的,然而IE也会为自定义特性创建属性。
B、直接给DOM对象设置属性值时,会同步修改相应节点元素的特性值,但是一般浏览器中自定义属性不会成为元素的特性,使用getArrtibute()时会返回null,然而IE会为自定义属性创建特性。
(3)HTMLElement类型是HTML中元素的基类型,对应不同的标签,还有很多更加具体的子类型,这些子类型也有与之相关的特性和方法,比如对应<body>标签有HTMLBodyElement类型、对应<table>标签有HTMLTableElement类型等。
(4)元素的子节点,可以有任意数目的子节点和后代节点,childNodes属性则包括了所有直接子节点,但是由于不同浏览器在处理注释、文本等节点的不同,会使得childNodes也不同,因此,如果需要遍历元素子节点的话,需要添加节点类型判断:
for(var i=0,l=element.childNodes.length; i < l; i++){ if(element.childNodes[i].nodeType === 1)//过滤元素子节点 { //对元素子节点做操作 } }
(5)元素的每一个特性都由一个Attr节点表示,每个节点都保存在一个NamedNodeMap对象中,这个对象和NodeList与HTMLCollection类似,是一个“动态”的,随文档变化而变化,它有下面的一些方法:
方法 | 说明 |
getNamesItem(name) | 返回nodeName等于name的节点 |
removeNamedItem(name) | 从列表中移除nodeName等于那么的节点,调用removeNamedItem()与在元素上调用removeAttribute()效果相同,只是前者返回被移除的Attr节点 |
setNamedItem(node) | 向列表中添加节点,以节点的nodeName属性为索引 |
item(pos) | 返回位于数字pos位置处的节点 |
元素的attributes属性返回的就是一个NamedNodeMap对象,其中包含一系列Attr节点,每个节点的nodeName就是特性名称,nodeValue就是特性值,可以使用attributes属性来遍历元素的特性(原书第268页):
function outputAttributes(element){ var pairs = new Array(), attr; for(var i=0, len=element.attributes.length; i < len; i++){ attr = element.attributes[i]; if(attr.specified){//specified属性表示特性值是设置还是默认的,为true表示设置的 pairs.push(attr.nodeName + "=\"" +attr.nodeValue + "\""); } } return pairs.join(" ");//以空格连接各个特性并返回 }
6、Text类型
文本节点由Text类型表示,包含的是可以按字面解释的纯文本内容,可以包含转义后的HTML字符,但不能包含HTML代码,Text类型的主要属性和方法有:
类别 | 属性/方法 | 说明 |
属性 | length | 文本字符数 |
data | 文本字符,和nodeValue值相同 | |
方法 | appendData(text) | 将text添加到文本节点末尾 |
deleteData(offset,count) | 从offset指定的位置开始删除count个字符 | |
insertData(offset,text) | 从offset指定的位置插入text | |
replaceData(offset,count,text) | 用text替换从offset指定的位置开始到offset+count为止处的文本 | |
splitText(offset) | 从offset指定的位置将当前文本节点分成两个文本节点 | |
substringData(offset,count) | 提取从offset指定的位置开始到offset+count为止处的字符串 |
说明:
(1)在Node类型中定义了一个normalize()方法,用于将相邻的两个或多个文本子节点合并,需要注意的是这个方法需要在文本节点的父节点上调用;与之相反的是,Text类型提供了splitText()方法,它会将原文本节点分成两个文本节点,原文本节点将包含从开始到指定位置之前的内容,新文本节点包含剩下的内容,最终返回新文本节点。
(2)默认情况下,每个可以包含内容的元素最多只能有一个文本子节点,而且文本不能为空。通过DOM脚本操作时,可能存在有多个文件子节点的情况。
(3)设置文本节点时需要注意,字符串会经过HTML或XML编码。
7、DocumentFragment类型
在所有节点类型中,只有DocumentFragment是没有对应的标记的,DOM规定DocumentFragment是一种“轻量级”的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源。DocumentFragment类型可以作为一个容器来使用,可以把后面要对文档进行的添加、修改、移除等操作先对DocumentFragment进行,然后再将DocumentFragment添加至文档中,从而避免DOM视图的多次渲染。例如:
function addItems(){ var fragment = document.createDocumentFragment();//创建一个文档片段作为容器 var ul = 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);//将列表元素添加中容器中,此时不会渲染页面 } ul.appendChild(fragment); //将容器中的节点添加到文档中(但容器本身不会添加至文档树),这样只需要渲染一次页面 }
需要注意的是,在DocumentFragment中的节点不属于文档,如果将文本中的节点添加至DocumentFragment中,该节点将会从文档树中移除。
8、操作表格
<table>元素是HTML中最复杂的结构之一,在HTML DOM中还为<table>、<tbody>、<tr>等元素添加了一些属性和方法(请注意table、tbody、tr及td之间的关系):
元素 | 类别 | 属性/方法 | 说明 |
<table> | 属性 | caption | 保存着对<caption>元素(如果有)的指针 |
tBodies | 是一个<tbody>元素的HTMLCollection | ||
tHead | 保存着对<thead>元素(如果有)的指针 | ||
tFoot | 保存着对<tfoot>元素(如果有)的指针 | ||
rows | 表格中所有行的HTMLCollection | ||
方法 | createTHead() | 创建<thead>元素,将其放到表格中,返回引用 | |
createTFoot() | 创建<tfoot>元素,将其放到表格中,返回引用 | ||
createCaption() | 创建<caption>元素,将其放到表格中,返回引用 | ||
deleteTHead() | 删除<thead>元素 | ||
deleteTFoot() | 删除<tfoot>元素 | ||
deleteCaption() | 删除<caption>元素 | ||
deleteRow(pos) | 删除指定位置的行 | ||
insertRow(pos) | 向rows集合中的指定位置插入一行 | ||
<tbody> | 属性 | rows | 保存着<tbody>元素中行的HTMLCollection |
方法 | deleteRow(pos) | 删除指定位置的行 | |
insertRow(pos) | 向rows集合中的指定位置插入一行,返回新插入行的引用 | ||
<tr> | 属性 | cells | 保存着<tr>元素中单元格的HTMLCollection |
方法 | deleteCell(pos) | 删除指定位置的单元格 | |
insertCell(pos) | 想cells集合中的指定位置插入一个单元格,返回新插入单元格的引用 |
注意,上表中列出的是原书中提及元素的属性和方法,并不全面,比如对应<td>元素还有HTMLTableCellElement对象,而<tr>对应对象还有rowIndex属性等,实际使用时可以查阅相关的DOM参考手册。这里旨在通过主要的一些属性和方法明确概念,而非参考大全。