DOM(文档对象模型)是针对HTML和XML文档的一个API(应用程序编程接口).
DOM描绘了一个层次化的节点树,允许添加,移除和修改页面的某一部分.
注意:IE中的所有DOM对象都是以COM对象的形式实现的.这意味着DOM对象与原生JavaScript对象的行为或活动特点并不一致.
10.1 节点层次
DOM描绘出的由多层节点构成的结构,每个节点都拥有各自的特点,数据和方法,也与其他节点存在某种关系,这种关系构成了层次,所有页面标记则表现为一个以特定节点为根节点的树形结构.
文档节点是每个文档的根节点,文档节点子节点,称为文档元素,它是文档的最外层元素,文档中其他所有元素都包含在文档元素中.每个文档只能有一个文档元素.
在HTML页面中,文档元素始终都是<html>元素.在XML中,没有预定义的元素,因此任何元素都可能成为文档元素.
每一段标识都可以通过树中一个节点表示:HTML元素--元素节点,特性(attribute)--特性节点,文档类型--文档类型节点,注释--注释节点.
10.1.1 Node类型
DOM1级定义了一个Node接口,该接口将由DOOM中的所有节点类型实现.这个Node接口在js中是作为Node类型实现的,除了IE之外,在其他所有浏览器中都可以访问这个类型.JS中所有节点类型都继承自Node类型,因此所有节点类型都共享着相同着基本属性和方法.
每个节点都有一个nodeType属性,用于表明节点的类型.节点类型由在Node类型中定义的下列12个数值常量来表示,任何节点类型必居其一:
Node.ELEMENT_NODE(1);
Node.ATTRIBUTE_NODE(2);
Node.TEXT_NODE(3);
Node.CDATA_SECTION_NODE(4);
Node.ENTITY_REFERENCE_NODE(5);
Node.ENTITY_NODE(6);
Node.PROCESSING_INSTRUCTION_NODE(7);
Node.COMMENT_NODE(8);
Node.DOCUMENT_NODE(9);
Node.DOCUMENT_TYPE_NODE(10);
Node.DOCUMENT_FRAGMENT_NODE(11);
Node.NOTATION_NODE(12);
如果我们想知道一个节点是不是元素节点,我们可以通过比较它的nodeType和Node.ELEMENT_NODE是否相等来知道,但这种方法在IE下会报错,因为最好的办法是用节点的nodeType与数字值1来比较,这在所有浏览器下都是支持的.
对于元素节点,它的nodeName保存的始终是元素的(大写的)标签名,而nodeValue的值始终为null.
每个节点都有一个childNodes属性,其中保存着一个NodeList对象.NodeList是一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点.
NodeList对象是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反映在NodeList对象中.
对于类数组对象(例如arguments,NodeList),可以用Array.prototype.slice()方法将其转换为数组.
//在IE8及之前版本中无效 var arrayOfNodes=Array.prototype.slice.call(someNode.childNodes,0);
如果想在所有IE中将NodeList转换为数组,必须手动枚举所有成员.下面代码在所有浏览器中都可以运行:
function convertToArray(nodes){ var array=null; try{ array=Array.prototype.slice.call(nodes,0);//针对非IE浏览器 }catch(ex){ array=new Array(); for(var i=0,len=nodes.length;i<len;i++){ array.push(nodes[i]); } } return array; }
每个节点都有一个parentNode属性,该属性指向文档树中的父节点.
包含在childNodes列表中的每个节点都可以通过使用previousSibling和nextSibling来访问同一列表的其他节点.
childNodes列表中第一个节点的previousSibling为null,列表中最后一个的nextSibling为null.
父节点的firstChild和lastChild分别指向列表中第一个和最后一个节点.
hasChildNodes()方法在节点包含一个或多个子节点的情况下返回true.
所有节点都有的最后一个属性是ownerDocument,该属性指向表示整个文档的文档节点.这种关系表示的是任何节点都属性它所有的文档,任何节点都不能同时存在于两个或更多个文档中.通过这个属性,我们可以不必在节点层次中通过层层回溯到达顶端,而是可以直接访问文档节点.
appendChild()用于向childNodes列表的末尾添加一个节点.这里添加的节点会直接改变结构.
任何DOM节点不能同时存在出现在文档中的多个位置上,所以如果调用appendChild()传入一个已经存在的节点,那么这个已经存在的位置就会改变.
insertBefore(要插入的节点,作为参照的节点):
把节点放在某个节点之前,如果作为参照的节点为null,则插到最后,此时和appendChild()相同.
replaceChild(要插入的节点,要替换的节点):
要替换的节点将由这个方法返回,并从文档树中被移除,插入的节点占据移除的节点位置.
如果只想移除而非替换节点,可以作用removeChild()方法.它接收一个参数,即要移除的节点.被移除的节点将成为方法的返回值.
并不是所有类型的节点都有子节点,如果不支持子节点的节点调用了上面方法会导致错误.
有两个方法是所有类型节点都有的,一个是cloneNode(),一个是normalize().
cloneNode(布尔值):创建一个完全相同的副本,布尔值为true时深复制,也就是复制节点及其整个子节点树,布尔值为false时浅复制,即只复制节点本身.
复制后返回的节点副本属于文档所有,但并没有为它指定父节点,算是个没有位置的"孤儿".
cloneNode()只复制结构,不复制事件.但IE在这里存在一个bug,它会复制事件,所以在复制之前最好先移除事件.
normalize()唯一的作用就是处理文档树中的文本节点,当某个节点上调用这个方法时,就会在后代节点中查找,如果找到空文本节点,则删除它,如果找到相邻的文本节点,则将它们合并为一个文本节点.
10.1.2 Document类型
JavaScript通过Document类型表示文档.在浏览器中,document对象是HTMLDocument(继承自Document类型)的一个实例,表示整个HTML页面.
document对象是window对象的一个属性,可以将其作为全局对象来访问.
Document节点具有下列特征:
nodeType值为9;
nodeName的值为"#document";
nodeValue的值为null;
parentNode的值为null;
ownerDocument的值为null;
其子节点可能是一个DocumentType(最多一个),Element(最多一个),ProcessingInstruction或Comment.
documentElement属性始终指向HTML页面中的<html>元素,childNodes列表可以访问文档元素.
document对象还有一个body属性,直接指向<body>元素,用document.body来引用<body>.
所有浏览器都支持document.documentElement和document.body属性.
可以通过document.doctype来引用<!DOCTYPE>.
不过浏览器对document.doctype的支持差别很大.
document对象的title属性,包含着<title>元素的文本--显示在浏览器窗口的标题栏或标签页上.
通过title属性可以读也可改当前页面的标题,并反映在浏览器的标题栏中.修改title属性的值不会改变<title>元素.
URL属性中包含页面完整的URL,domain属性只包含页面的域名,referrer属性中则保存着链接到当前页面的那个页面的URL.
getElementById(),接收一个参数:要取得元素的ID.这里的ID必须与元素的id严格匹配,包括大小写.
如果页面中多个元素的ID值相同,getElementById()只返回第一次出现的元素.
getElementByTagName(),接收一个参数:即要取得元素的标签名,而返回的是包含零或多个元素的NodeList.这个方法会返回一个HTMLCollection对象,它是一个"动态"的集合,类数组.
HTMLCollection对象还有一个方法namedItem(),使用这个方法可以通过元素的name特性取得集合中的项.
document.getElementsByTagName(“*")取得文档中所有元素.
getElementsByName(),这个方法会返回带有给定name特性的所有元素.
document.anchors,包含文档中所有带name特性的<a>元素;
document.forms,包含文档中所有的<form>元素,与document.getElementsByTagName(“form”)得到的结果相同;
document.images,包含文档中所有的<img>元素,与document.getElementsByTagName(“img”)得到的结果相同.
document.links,包含文档中所有带href特性的<a>元素.
DOM一致性检测:由于DOM分为多个级别,也包含多个部分,因此检测浏览器实现了DOM的哪个部分十分必要,document.implementtation属性就是为此提供相应信息和功能的对象,它规定了一个方法hasFeature(检测的DOM功能的名称,检测DOM功能版本号),如果浏览器支持给定名称和版本的功能,则该方法返回true.
document对象将输出流写入到网页中的方法:write(),writeln(),open(),close().
write()和writeln()方法接受一个字符串参数,即要写入到输出流中的文本.
write()会原样写入,而writeln()会在字符串的末尾添加一个换行符( ).在页面被加载的过程中,可以使用这两个方法向页面中动态地加入内容.
如果在文档加载结束后再调用document.write(),那么输出的内容将会重写整个页面.
10.1.3 Element类型
Element节点特征:
nodeType值为1;
nodeName的值为元素的标签名;
nodeValue的值为null;
parentNode的值可能为Document或Element;
其子节点可能是Element,Text,Comment,ProcessingInstruction,CDATASection或EntityReference.
要访问元素的标签名,可以使用nodeName属性,也可以使用tagName属性;这两个属性会返回相同的值.
在HTML中,标签名始终都以全部大写表示;而在XML中,标签名则始终与源代码中的保持一致.
每个元素都有一个或多个特性,这些特性的用途是给出相应元素或其内容的附加信息.操作特性的DOM方法主要有三个,分别是getAttribute(),setAttribute()和removeAttribute().这三个方法可以针对任何特性使用,包括那些以HTMLElement类型属性的形式定义的特性.
getAttribute()也可访问自定义特性值,特性的名称是不区分大小写的.
在开发中,经常不使用getAttribute(),而是只使用对象的属性.只有在取得自定义特性值的情况下,才会使用getAttribute()方法.
setAttribute(要设置的特性名,要设置的特性值):如果特性存在,会替换现有的,如果不存在,会创建并设置.
因为所有的特性都是属性,所以直接给属性赋值可以设置特性的值.
removeAttribute(),这个方法用于彻底删除元素的特性.调用这个方法不仅会清除特性的值,而且也会从元素中完全删除特性.这个方法并不常用.
attributes属性中包含一个NamedNodemap,也是一个"动态"的集合.元素的每一个特性都由一个Attr节点表示,每个节点都保存在NamedNodeMap对象中.
NamedNodeMap对象拥有下列方法:
getNamedItem(name):返回nodeName属性等于name的节点;
removeNamedItem(name):从列表中移除nodeName属性等于name的节点;
setNamedItem(node):向列表中添加节点,以节点的nodeName属性为索引;
item(pos):返回位于数字pos位置处的节点.
针对attributes对象中的特性,不同浏览器返回的顺序是不同的.
以下代码展示了如何迭代元素的每一个特性,然后将它们构成name=”value” name=”value”这样的字符串格式.
function outputAttributes(element){ var pairs=new Array(), attrName, attrValue, i, len; for( var i = 0,len=element.attributes.length;i<len;i++ ){ attrName=element.attributes[i].nodeName; attrValue=element.attributes[i].nodeValue; if(element.attributes[i].specified){ pairs.push(attrName+="=""+attrValue+"""); } } return pairs.join(" "); }
document.createElement()方法可以创建新元素,不过新元素未被添加到文档树中,想添加到文档树中可以用appendChild(),insertBefore(),replaceChild().
10.1.4 Text类型
文本节点由Text类型表示,包含的是可以照字面解释的纯文本内容.纯文本中可以包含转义后的HTML代码,但不能包含HTML代码.
Text节点有以下特征:
nodeType值为3;
nodeName的值为"#text";
nodeValue的值为节点所包含的文本;
parentNode是一个Element;
不支持(没有)子节点.
可以通过nodeValue属性或data属性访问Text节点中包含的文本,这两个属性中包含的值相同.对nodeValue的修改也会通过data反映出来,反之亦然.
使用下列方法可以操作节点中的文本.
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为止处的文本.
如果使用createElement创建元素,而没有添加到文档中,元素是不会显示的,要使用appendChild添加到文档元素中。
一般情况下,每个元素只有一个文本子节点,不过如果两个文本节点是相邻的同胞节点,两个文本节点创建出来后相继添加到元素里,它们会连接起来,中间不会有空格。
可以使用normalize()将多个文本子节点合成一个文本子节点,结果节点的nodeValue等于将合并前每个文本节点的nodeValue值拼接起来的值。
splitText()是和normalize()完全相反的方法,它将一个文本节点分成两个文本节点,括号里传入一个分割位置的值,原来的是到这个分割点的文本,新文本节点是剩下的内容。这个方法返回一个新文本节点,这个节点与原节点的parentNode相同。
10.1.5 Comment类型
Comment节点有以下特征:
nodeType值为8;
nodeName的值为"#conmment";
nodeValue的值为注释的内容;
parentNode可能是Document或Element;
不支持(没有)子节点。
10.1.6 CDATASection类型
CDATASection节点有以下特征:
nodeType值为4;
nodeName的值为"#cdata-section";
nodeValue的值为CDATA中的内容;
parentNode可能是Document或Element;
不支持(没有)子节点。
10.1.7 DocumentType类型
nodeType值为10;
nodeName的值为doctype的名称;
nodeValue的值为null;
parentNode是Document;
不支持(没有)子节点。
10.1.8 DocumentFragment类型
nodeType值为11;
nodeName的值为”#document-fragment”;
nodeValue的值为null;
parentNode是null;
子节点可以是Element,ProcessingInstruction,Comment,Text,CDATASection或EntityReference.
10.1.9 Attr类型
nodeType值为2;
nodeName的值为特性的名称;
nodeValue的值为特性的值;
parentNode的值为null;
在HTML中不支持(没有)子节点;
在XML中子节点可以是Text或EntityReference.
10.2 DOM操作技术
10.2.1动态脚本
在页面加载时不存在,但在将来某个时刻通过修改DOM动态添加的脚本。
<script type="text/javascript" src="client.js"></script>
上面语句可以用下面语句来解释:
var script=document.createElement('script');
script.type='text/javascript';
script.src='client.js';
document.body.appendChild(script);
IE将<script>视为一个特殊的元素,不允许DOM访问其子节点。
可以写一个函数,先尝试用标准DOM文本节点方法,除了IE会拋出错误外其他浏览器都支持,可以用try-catch语句来捕获IE抛出的错误,然后针对IE的特殊方法来设置样式。
function loadScriptString(code){
var script=document.createElement('script');
script.type='text/javascript';
try{
script.appendChild(document.createTextNode(code));
}catch(ex){
script.text=code;
}
document.body.appendChild(script);
}
//调用函数
loadScriptString("function sayHi(){alert('hi');}");
10.2.2 动态样式
动态样式是指在页面刚加载时不存在的样式,但在页面加载完成后动态添加到页面中的。
将<link>元素添加到<head>里而不是<body>,才能保证所有浏览器的行为一致。
加载外部样式文本的过程是异步的,也就是加载样式与执行JavaScript代码的过程没有固定的次序。
10.2.3 操作表格
10.2.4 使用NodeList
应该尽量减少访问NodeList的次数,因为每次访问NodeList,都会运行一次基于文档的查询。所以,可以考虑将从NodeList中取得的值缓存起来。
10.3 小结
DOM是语言中的API,用于访问和操作HTML和XML文档。DOM1级将HTML和XML文档形象地看作一个层次化的节点树,可以使用JavaScript来操作这个节点树,进而改变底层文档的外观和结构。
DOM由各种节点构成,简要总结如下:
最基本的节点类型是Node,用于抽象地表示文档中一个独立的部分; 所有其他类型都继承自Node。
Document类型表示整个文档,是一组分层节点的根节点。在JavaScript中,document对象是Document的一个实例。使用document对象,有很多种方式可以查询和取得节点。
Element节点表示文档中的所有HTML和XML元素,可以用来操作这些元素的内容和特性。
DOM操作往往是JavaScript程序中开销最大的部分,而因访问NodeList导致的问题为最多。NodeList对象都是“动态的”,这就意味着每次访问NodeList对象,都会运行一次查询。有鉴于此,最好的办法就是减少DOM操作。