摘要:年月级规范成为的推荐标准,为基本的文档结构及查询提供了接口。这意味着中的对象与原生对象的行为或活动特点并不一致。结果第一条注释就会成为中的第一个子节点。由于跨域安全限制,来自不同子域的页面无法通过通信。
DOM(文档对象模型)是针对HTML和XML文档的一个API(应用程序编程接口)。DOM描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。DOM脱胎于Netscape及微软公司创始的DHTML(动态HTML),但现在它已经成为表现和操作页面标记的真正跨平台、语言中立方式。
1998年10月DOM 1 级规范成为W3C的推荐标准,为基本的文档结构及查询提供了接口。本章主要讨论与浏览器中的HTML页面相关的DOM1级的特性和应用,以及JavaScript对DOM1级的视线。
IE中的所有DOM对象都是以COM对象的形式实现的。这意味着IE中的DOM对象与原生JavaScript对象的行为或活动特点并不一致。本章将较多的谈及这些差异。
节点层次Sample Page Hello World!
可以将这个简单的HTML文档视为一个层次结构,如图10-1所示。
文档节点是每个文档的根节点。在这个例子中,文档节点只有一个子节点,既元素,我们称之为文档元素。
文档元素是文档的最外层元素,文档中的其他所有元素都包含在文档元素中。每个文档只能有一个文档元素。在HTML页面中,文档元素始终都是元素。在XML中,没有预定义的元素,因此任何元素都可能成为文档元素。
Node 类型DOM1级定义了一个 Node 接口,该接口将由 DOM 中所有节点类型实现。这个Node接口在JavaScript中是作为Node类型实现的;除了IE之外,在其他所有浏览器中都可以访问到这个类型。
JavaScript中的所有节点类型都继承自Node类型,因此所有节点类型都共享着相同的基本属性和方法。
每个节点都有一个nodeType属性,用于表明节点的类型。及诶单类型由在Node类型中定义的下列12个数值常量来表示,任何节点类型必居其一(编号为节点类型常量存储的数值):
Node.ELEMENT_NODE
Node.ATTRIBUTE_NODE
Node.TEXT_NODE
Node.CDATA_SECTION_NODE
Node.ENTITY_REFERENCE_NODE
Node.ENTITY_NODE
Node.PROCESSING_INSTRUCTION_NODE
Node.COMMENT_NODE
Node.DOCUMENT_NODE
Node.DOCUMENT_TYPE_NODE
Node.DOCUMENT_FRAGMENT_NODE
Node.NOTATION_NODE
// 通过比较上面的常量,很容易的确定节点类型 // 在IE中无效 if (someNode.nodeType == Node.ELEMENT_NODE) { console.log("Node is an element"); } // 由于IE没有公开 Node 类型的构造函数 // 最好还是将 nodeType 属性与数字比较 if (someNode.nodeType == 1) { console.log("Node is an element"); }
并不是所有节点类型都受到Web浏览器的支持。开发人员最常用的就是元素和文本节点。
nodeName 和 nodeValue 属性了解节点的具体信息,可以使用nodeName 和 nodeValue 两个属性。这两个属性的值完全取决于节点类型。在使用这两个值以前,最好用上述的代码检查节点的类型。
if (someNode.nodeType == 1) { value = someNode.nodeName; // nodeName的值是元素的标签名 }节点关系
每个节点都有一个childNodes属性,其中保存着一个NodeList对象。注意,可以通过方括号语法来访问NodeList的值,而且也有length属性,但它并不是Array的实例。
NodeList对象的独特之处在于,它实际上是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反映在NodeList对象中。我们常说NodeList是有生命、有呼吸的对象,而不是我们第一次访它的瞬间拍摄下来的一张快照。
// 方括号和 item() 语法结果是相同的 var firstChild = someNode.childNodes[0]; var secondChild = someNode.childNodes.item(1); var count = comeNode.childNodes.length; // 虽然不是Array的实例,但我们可以将它转换成数组 // 在IE8及之前的版本中无效 var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes, 0);
// 由于IE8及更早版本将 NodeList 实现为一个 COM 对象 // 必须手动枚举所有成员,才能转换成数组 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 属性可以访问同一列表中的其他节点。列表第一个节点previousSibling为null,列表最后一个nextSibling为null,当然如果列表只有一个节点,那么两个都是null。
父节点的firstChild和lastChild属性分别指向第一个和最后一个。如果列表没有节点,那么两个属性都是null。
hasChildNodes()也是一个非常有用的方法,当查询节点存在子节点时返回true,不存在返回false。这是比查询childNodes.length更简单的方法。
所有节点都有的最后一个属性是ownerDocument,该属性指向表示整个文档的文档节点。这种关系表示的是任何节点都属于它所在的文档,任何节点都不能同时存在两个或更多个文档中。通过这个属性,我们可以不必在节点层次中通过层层回溯达到顶端,而是可以直接访问文档节点。
操作节点因为关系指针都是只读的,所以DOM提供了一些操作节点的方法 。
最常用的方法是appendChild(),用于向childNodes列表的末尾添加一个节点,执行后,方法返回新增的节点。
var returnedNode = someNode.appendChild(newNode); console.log(returnedNode == newNode); // true console.log(someNode.lastChild ==newNode); // true
如果需要把节点放在childNodes列表中某个特定的位置上,而不是放在末尾,可以使用insertBefore()方法。这个方法接收两个参数:要插入的节点和作为参照的节点。插入节点后,被插入的节点会变成参照节点的前一个同胞节点(previousSibling),同时被方法返回。如果参照节点是null,则 insertBefore() 和 appendChild() 执行相同操作。
// 插入后成为最后一个子节点 var returnedNode = someNode.insertBefore(newNode, null); // 插入后成为第一个子节点 var returnedNode = someNode.insertBefore(newNode, someNode.firstChild); // 插入后在最后一个子节点前面 var returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
replaceChild() 替换节点。同样接收两个参数,插入的节点和参照节点。插入新的节点并将参照节点从文档树中移除,新的节点会从被替换的节点复制所有关系指针。尽管从技术上讲,被替换的节点仍然在文档中,但它在文档中的位置已经不存在了。
removeChild() 移除节点。被移除的节点仍然在文档中,但它在文档中的位置已经不存在了。
以上四个方法必须先取得操作节点的父节点(代码示例中是someNode)。在不存在子节点的节点上调用以上方法,会导致错误。
其他方法还有两个方法是所有节点都有的。
cloneNode() 用于创建调用这个方法的节点的一个完全相同的副本。接收一个布尔值参数,表示是否执行深复制。
传入true。执行深复制,复制节点及其整个子节点树
传入false。执行浅复制,即只复制节点本身。
复制返回的节点副本属于文档所有,但并没有为它制定父节点。因此这个节点副本就成为了一个“孤儿”,除非通过 appendChild() insertBefore() replaceChild() 将它添加到文档中。
IE8及之前的版本不会为包含空白符的文字创建节点(TEXT)
clone() 方法不会复制添加到DOM节点中的JavaScript属性,例如时间处理程序。这个方法只复制特性、(在明确指定的情况下也复制)子节点,其他一切都不会复制。
IE 会复制事件处理程序,所以我们建议在复制之前最好先移出事件处理程序。
var myList = document.getElementById("ul"); var deepList = myList.cloneNode(true); // [text, li, text, li, text, li, text] console.log(deepList.childNodes); // 3 (IE < 9) 或 7 (其他浏览器) // IE8及之前的版本不会为包含空白符的文字创建节点(TEXT) console.log(deepList.childNodes.length); var shallowList = myList.cloneNode(false); console.log(shallowList.childNodes.length); // 0
normalize() 方法唯一的作用就是处理文档树中的文本节点。由于解析器的实现或DOM操作等原因,可能会出现文本节点不包含文本,或者连接出现两个节点的情况。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。
如果找到了空文本节点,则删除它
如果找到相邻的文本节点,则将它们合并为一个文本节点
本章后面还将进一步讨论方法
var html = document.documentElement; // 取得对的引用 console.log(html == document.childNodes[0]); // true console.log(html == document.firstchild) // true
所有浏览器都支持document.documentElement 和 document.boyd 属性
Document另一个可能的子节点是DocumentType。通常将标签看成一个与文档其他部分不同的实体,可以通过doctype属性(在浏览器中是document.doctype)来访问信息。
浏览器对document.doctype的支持差别很大,所以这个属性的用途很有限:
IE8及之前版本,如果存在文档类型声明,会将其错误的解释为一个注释并把它当做Comment节点;而document.doctype的值始终为null
IE9+,如果存在文档类型声明,则将其作为文档的第一个子节点;document.doctype是一个DocumentType节点,也可以通过document.firstChild或document.childNodes[0]访问同一个节点。
Safari, Chrome, Opera :如果存在文档类型声明,则将其解析,但不作为文档的子节点。document.doctype是一个DocumentType节点,但该节点不会出现在document.childNodes中。
从技术上说,出现在元素外部的注释应该是算是文档的子节点。然而,不同的浏览器在是否解析这些注释以及能否正确处理他们等方面,也存在很大差异。
看起来这个页面应该有3个子节点:注释、元素、注释。从逻辑上讲,我们会认为document.childNodes中应该包含与这3个节点对应的3项。但是实际上,浏览器存在以下差异:
IE8及之前版本、Safari3.1及更高版本、Opera和Chrome 只为第一条注释创建节点,不为第二条注释创建节点。结果第一条注释就会成为document.childNodes中的第一个子节点。
IE9+,将会将两条都创建节点。
Firefox 和 Safari3.1之前的版本会完全忽略这两条注释。
多数情况下,我们都用不着在document对象上调用appendChild() removeChild() replaceChild() 方法,因为文档类型(如果存在的话)是只读的,而且它只能有一个元素子节点(该节点通常早就已经存在了)。
文档信息作为HMLTDocument的一个实例,document对象还有一些标准的Document对象所没有的属性。
title 包含着
// 取得文档标题 var originalTitle = document.title; // 设置文档标题 document.title = "New page title";
下面三个属性与网页的请求有关,所有这些信息都存在于请求的HTTP头部,只不过是通过这些属性让我们能够在JavaScript中访问它们而已:
URL属性中包含页面完整的URL(地址栏中的URL)
domain属性中值包含页面的域名
referrer属性中可能会包含空字符串
URL与domain属性是相互关联的。例如document.URL等于"http://www.wrox.com/WileyCDA/",那么document.domain就等于"www.wrox.com"。
3个属性中只有domain可以设置,但有安全方面的限制。如果URL中包含一个子域名,例如"p2p.wrox.com",那么就只能讲domain设置为"wrox.com"(URL中包含"www",如"www.wrox.com"时,也是如此)。
当页面中包含来自其他子域的框架或内嵌框架时,能够设置document.domain就非常方便了。由于跨域安全限制,来自不同子域的页面无法通过JavaScript通信。而通过将每个页面的document.domain设置为相同的值,这些页面就可以互相访问对方包含的JavaScript对象了。
// 取得完整的URL var url = document.URL; // 取得域名 var domain = document.domain; // 取得来源 页面的URL var referrer = document.referrer;
浏览器对domain属性还有一个限制,即如果域名一开始是“松散的”(loose),那么就不能将它再设置为“紧绷的”(tight)。
// 假设页面来自于 p2p.wrox.com域 document.domain = "wrox.com"; // 松散的(成功) document.domain = "p2p.wrox.com"; // 紧绷的(出错)查找元素
getElementById() 接收一个参数:要取得的元素的ID。找到相应的元素则返回该元素,否则返回null。
IE8及较低版本不区分ID大小写
如果页面多个元素的ID相同,只会返回第一个匹配的元素。
IE7及更早的版本添加了一个怪癖:name特性与给定ID匹配的表单元素也会被该方法返回。
A div
getElementsByTagName() 接收一个参数:要取得的元素的标签名,而返回的是包含零或多个元素的NodeList。可以使用方括号语法或item()方法来访问对象中的项。
namedItem() 使用这个方法可以通过元素的name特性取得集合中的项。或方括号语法能达到同样的效果
要取得文档中的所有元素,可以向getElementsByTagName()中传入"*"。在JavaScript及CSS中,星号通常表示全部。
虽然标准规定标签名需要区分大小写,但为了最大限度的与既有HTML页面兼容,传给getElementsByTagName()的标签名是不需要区分大小写的。但对于XML页面而言(包括XHTML),getElementsByTagName()方法就会区分大小写。
getElementByName() 是只有HTMLDocument类型才有的方法,返回带有给定name属性的所有元素。最常使用的情况是取得单选按钮;为了确保发送给浏览器的值正确无误,所有单选按钮必须具有相同的name特性
上述例子使用getElementsByName()方法可以返回三个input元素。但是对于这里的单选按钮来说namedItem()方法只会取得第一项(因为每一项的name特性都相同)。
特殊集合document.anchors 包含文档中所有带name特性的元素
document.applets 包含文档中所有的元素,与document.getElementsByTagName("form")得到的结果相同
document.images 包含文档中所有的元素,与document.getElementsByTagName("img")得到的结果相同
document.links 包含文档中所有带 href 特性的元素
DOM一致性检测由于DOM分为多个级别,也包含多个部分,因此检测浏览器实现了DOM的哪些部分就十分必要。document.implementation属性就是为此提供的,与浏览器对DOM的实现直接对应。
DOM1级别只为document.implementation规定了一个方法,即hasFeature()。接收两个参数:要检测的DOM功能的名称及版本号。如果支持返回true
var hasXmlDom = docuemnt.implementation.hasFeature("XML", "1.0");
下表列出了可以检测的不同值得版本号
功能 | 版本号 | 说明 |
---|---|---|
Core | 1.0、2.0、3.0 | 基本的DOM,用于描述表现文档的节点树 |
XML | 1.0、2.0、3.0 | Core的XML拓展,添加了对CDATA、处理指令及实体的支持 |
HTML | 1.0、2.0 | XML的HTML拓展,添加了对HTML特有元素及实体的支持 |
Views | 2.0 | 基于某些样式完成文档的格式化 |
StyleSheets | 2.0 | 将样式表关联到文档 |
CSS | 2.0 | 对层叠样式表1级的支持 |
CSS2 | 2.0 | 对层叠样式表2级的支持 |
Events | 2.0, 3.0 | 常规的DOM事件 |
UIEvents | 2.0, 3.0 | 用户界面事件 |
MouseEvents | 2.0, 3.0 | 由鼠标引发的事件(click、mouseover等) |
MutationEvents | 2.0, 3.0 | DOM树变化时引发的事件 |
HTMLEvents | 2.0 | HTML4.01事件 |
Range | 2.0 | 用于操作DOM树种某个范围的对象和方法 |
Traversal | 2.0 | 遍历DOM树的方法 |
LS | 3.0 | 文件与DOM树之间的同步加载和保存 |
LS-Asnyc | 3.0 | 文件与DOM树之间的异步加载和保存 |
Validation | 3.0 | 在确保有效的前提下修改DOM树的方法 |
hasFeature() 方法确实方便,但也有缺点。因为实现者可以自行决定是否与DOM规范的不同部分保持一致。事实上,想让hasFearture()针对所有值都有返回true很容易,但返回true有时候也不意味着实现与规范一致。
为此我们建议,在使用hasFreatrue()之外,还同时使用能力检测。
文档写入write()和writeln()方法都接收一个字符串参数,即要写入到输出流中的文本。wirte()会原样写入,而writeln()则会在字符串的末尾添加一个换行符(n)。在页面加载的过程中,可以使用这两个方法动态的加入内容。
在包含JavaScript文件时,必须注意不能像下面的例子那样直接包含字符串"",因为这会导致该字符串被解释为脚本块的结束,后面的代码将无法执行。使用转义""可以避免这个问题。
open()和close()分别用于打开和关闭网页的输出流。如果是在页面加载期间使用write()或writeln()方法,则不需要用到这两个方法。
严格型XHTML文档不支持文档吸入。对于那些按照application/xml+xhtml内容类型提供的页面,这两个方法也同样无效。
Element类型Element类型用于表现XML或XHTML元素,提供了对元素标签名、子节点及特性的访问。
Element类型具有以下特征:
nodeType的值为1
nodeName的值为元素的标签名
nodeValue的值为null
parentNode的值可能为Dcoment或Element
其子节点可能是 Element、 Text 、 Comment 、 ProcessingInstruction、 CDATASection、 EntityReference
访问元素的标签名,可以使用nodeName属性,也可以是使用tagName属性,这两个属性会返回相同的值。
var div = document.getElementById("myDiv"); console.log(div.tagName); // "DIV" console.log(div.nodeName); // "DIV" console.log(div.tagName == div.nodeName); // true
if (element.tagName == "div") { // 不能这样比较,很容易出错 } if (element.tagName.toLowerCase() == "div") { // 推荐这样做(适用于任何文档) }HTML元素
所有HTML元素都由HTMLElement类型表示。HTMLElement类型直接继承自Elment并添加了一些属性。每个HTML元素中都存在的下列标准特性:
id 元素在文档中的唯一标识符
title 有关元素的附加说明信息,一般通过工具提示条显示出来
lang 元素内容的语言代码,很少使用
dir 语言的方向值为"ltr"(left-to-right 从左至右)或 "rtl"
className 与元素的class特性对应,即为元素指定的CSS类。没有将这个属性命名为class是因为class是ECMAScript的保留字。
并不是对所有属性的修改都会在页面中直观的表现出来。对id或lang的修改对用户而言是透明不可见的。而对title的修改则只会在鼠标移动到这个元素之上时才会显示出来。对dir的修改会在属性重写的那一刻,立即影响页面中文本的左右对齐方式。修改className时,如果新类关联了与此前不同的CSS样式,就立即应用新的样式。
下面表格列出了所有HTML元素以及与之关联的类型(以斜体印刷的元素表示不推荐使用了)。注意表中的这些类型在Opera、Safari、Chrome、Firefox中都可以通过JavaScript访问,但在IE8之前的版本中,不能通过JavaScript访问。
操作特性的DOM方法主要有三个,分别是getAttribute()、setAttribute()、 removeAttribute()。
var div = document.getElemntByid("myDiv"); console.log(div.getAttribute("id")); // "myDiv" console.log(div.getAttribute("class")); // "bd" console.log(div.getAttribute("title")); // "Body Text" console.log(div.getAttribute("lang")); // "en" console.log(div.getAttribute("dir")); // "ltr"
注意,传递给getAttribute()的特性名与实际的特性名相同。因此想要得到class特性值,应该传入"class" 而不是"className",后者只在通过对象属性访问特性时才用。
如果给定的特性不存在,getAttribute()返回null。
也可以取得自定义特性,即标准HTML语言中没有的特性的值。需要注意,特性的名称不区分大小写,即"ID" 和 "id" 代表的都是同一个特性。另外也要注意,根据HTML5规范,自定义特性应该加上data-前缀以便验证。
任何元素的所有特性,也都可以通过DOM元素本身的属性来访问。当然HTMLElement也会有5个属性与相应的特性一一对应。不过只有公认的(非自定义)特性才会以属性的形式添加到DOM对象中。例如可以通过div.id访问div元素的id属性。不过自定义特性在Safari、Opera、Chrome、Firefox中是不存在的,但IE却会为自定义特性也创建属性。
CSS通过getAttribute()访问时,返回的style特性值中包含的是CSS文本,而通过属性来访问它则会返回一个对象。由于style属性是用于以编程方式访问元素样式的(本章后面讨论),因此并没有直接映射到style特性。
时间处理程序(例如onclick)通过getAttribute()访问,返回的是相应的代码字符串。而在访问onclick属性时,则返回的是一个JavaScript函数(如果未在元素中指定相应特性,则返回null)。这是因为onclick及其他事件程序属性本身就应该被赋予函数值。
由于存在上述差别,在通过JavaScript以编程方式操作DOM时,开发人员不经常使用 getAttribute()方法,而只是使用对象的属性。只有在取得自定义特性值得情况下,才会使用getAttribute()方法。
在IE7及以前版本中,通过getAttribute()访问style特性或onclick,返回的值与属性相同,都返回对象值或函数值。虽然IE8已经修复了这个bug,但不同IE版本间的不一致性,也是导致开发人员不适用getAttribute()访问HTML特性的一个原因。
设置特性与getAttribute()对应的方法时setAttribute()这个方法接收两个参数:要设置的特性名和值。如果特性已经存在,setAttribute()会以指定的值替换现有的值;如果特性不存在,则创建该属性并设置相应的值。
setAttribute()方法既可以操作HTML特性也可以操作自定义特性。通过这个方法设置的特性名会统一转换为小写形式,即"ID"最终变成"id"。
div.setAttribute("id", "someOtherId"); div.id = "someOtherId"; // 添加自定义属性,该属性不会自动成为元素的特性 div.mycolor = "red"; div.getAttribute("mycolor"); // null ie除外
removeAttribute() 用于彻底删除元素的特性,调用这个方法不仅会清楚特性的值,而且也会从元素中完全删除特性。这个方法并不常用,IE6及以前版本不支持。
div.removeAttribute("class");attributes属性
Element 类型是使用 attributes 属性的唯一一个DOM节点类型 。
attributes属性中包含一个NamedNodeMap,与NodeList类似,也是一个动态集合。元素的每一个 特性都由一个Attr节点表示,每个节点都保存在NamedNodeMap对象中。
NamedNodeMap对象拥有以下方法
getNamedItem(name):返回nodeName属性等于name的节点
removeNamedItem(name):从列表中移除nodeName属性 等于name的节点
setNameItem(node):向列表中添加节点,以节点的nodeName属性为索引
item(pos):返回位于数字pos位置处的节点
attributes属性中包含一系列节点,每个节点的nodeName就是特性的名称,而节点的nodeValue就是特性的值。
// 取得元素的id var id = element.attributes.getNamedItem("id").nodeValue; // 设置元素的id element.attributes["id"].nodeValue = "someOtherId"; // 删除元素id,并返回被删除特性的Attr节点 var oldAttr = element.attributes.removeNamedItem("id"); // 传入一个新的特性节点 element.attributes.setNameItem(newAttr);
由于attributes的方法不够方便,因此开啊人员更多的会使用getAttribute()、removeAttribute()、setAttribute()方法。如果想要遍历元素特性,可以用attributes
针对attributes对象中的特性,不同浏览器返回的顺序不同。
IE7及更早版本返回HTML元素中所有可能的特性,包括没有指定的特性。返回100多个特性是常见的
// 迭代元素的每一个特性,然后构造成 name="value"字符串 function outputAttributes(element) { var pairs = new Array(), attrName, attrValue, i, len; for (i=0, len=elment.attributes.length; i < len; i++) { attrName = element.attributes[i].nodeName; attrValue = element.attributes[i].nodeValue; // 针对 IE7- 做兼容 // 根据specified属性,只返回指定的特性 if (element.attributes[i].specified) { paris.push(attrName + "="" + attrValue + """); } } return pairs.join(" "); }创建元素
document.createElement()方法可以创建新元素。只接收一个参数,即要创建元素的标签名,在HTML文档中不区分大小写,而在XML(包括XHTML)文档中,则是区分大小写。
document.createElement()创建元素的同时,也为新元素设置了ownerDcoument属性。此时还可以操作元素的特性,为它添加更多子节点。
由于新元素尚未被添加到文档树中,因此设置这些特性不会影响浏览器的显示。要把新元素添加到文档树,可以使用appendChild() insertBefore() replaceChild()方法。
// 创建 var div = document.createElement("div"); // 操作元素特性,添加子节点 div.id = "myNewDiv"; div.className = "box"; document.body.appendChild(div);
在IE中可以传入完整的元素标签,也可以包含属性(仅IE支持)。这样有助于避开在IE7及更早版本中动态创建元素的某些问题:
不能设置动态创建的元素的name特性
不能通过表单的reset()方法重设动态创建的元素(第13章讨论reset()方法)
动态创建的type特性值为“reset”的元素重设不了表单
动态创建的一批name相同的单选按钮彼此毫无关系。
if (client.browser.id && client.browser.ie <= 7) { var div = document.createElement(""); }元素的子节点
元素可以有任意书目的子节点和后台节点,因为元素可以是其他元素的子节点。元素的childNodes属性中包含了它所有子节点,这些子节点可能是元素、文本节点、注释或处理指令。不用浏览器在看待这些节点方面存在显著的不同。
IE解析,元素会有3个子节点,分别是3个元素。但如果是其他浏览器,
元素都会有7个元素,包括3个元素和4个文本节点(表示元素之间的空白符)。
如果将元素间的空白符删除,那么所有浏览器都会返回相同数目的子节点
如果需要通过childNodes属性遍历子节点,那么一定不要忘记浏览器间的这一差别。这意味着在执行某项操作以前,通常都要先检查nodeType属性
for (var i=0, len = element.childNodes.length; i < len; i++) { if (element.childNodes[i].nodeTpe == 1) { ... } }
如果想通过某个特性的标签名取得子节点或后代节点,可以通过元素调用getElementsByTagName()方法,结果只会返回当前元素的后代。
var ul = document.getElementById("myList"); var items = ul.getElementsByTagName("li");Text类型
文本节点由Text类型表示,包含的是可以照字面量解释的纯文本内容。纯文本中可以包含转义后的HTML字符,但不能包含HTML代码。
Text节点具有以下特征:
nodeType的值为3
nodeName的值为"#text"
nodeValue的值为节点所包含的文本
parentNode是一个Element
不支持(没有)子节点
可以通过nodeValue属性或data属性访问Text节点中包含的文本,这两个属性的值相同。对nodeValue的修改也会通过data反映出来,反之亦然。
使用下列方法可以操作节点中的文本
appendData(text):将text添加到节点的末尾
deleteData(offset, count):从offset指定的位置插入text
insertData(offset, text):在offset指定的位置插入text
replaceData(offset, count, text):用text替换从offset指定的位置开始到 offset+count为止处的文本
splitText(offset):从offset指定的位置将当前文本节点分成两个文本节点。
substringData(offset, count):提取从offset指定的位置开始到 offset+count为止处的字符串
length属性:保存着节点中字符的书目。而且nodeValue.length和data.length中也保存着同样的数值
在默认情况下,每个可以包含内容的元素最多只能有一个文本节点,而且必须确实有内容存在
Hello World!
// 可以像这样取得文本子节点 var textNode= div.firstChild; // 或者 div.childNodes[0] // 取得文本节点的引用后,就可以修改它了 div.firstChild.nodeValue = "Some other message";
如果这个文本节点当前存在于文档树中,那么修改文本节点的结果就会立即得到反映。
修改文本节点时,字符串会经过HTML(或XML,取决于文档类型)编码。换言之,小于号、大于号或引号都会像下面的例子一样被转义
div.firstChild.nodeValue = "Some other message"; // 输出结果:"Some other message"
这是在向DOM文档中插入文本之前,先对其进行HTML编码的一种有效方式
创建文本节点document.createTextNode()创建新的文本节点。与设置已有文本节点的值一样,作为参数的文本也将按照HTML或XML的格式进行编码。
var textNode = document.createTextNode("Hello World!");
在创建新文本节点的同时,也会为其设置ownerDocument属性。不过除非把新节点添加到文档树中已经存在的节点中,否则我们不会在浏览器窗口中看到新节点。
var element = document.createElement("div"); elment.className = "message"; var textNode = document.createTextNode("Hello world!"); element.appendChild(textNode); document.body.appendChild(element);
一般情况下,每个元素只有一个文本子节点。不过在某些情况下也可能包含多个文字子节点。相邻的同胞文本节点,之间会连起来,中间不会有空格。
var element = document.createElement("div"); elment.className = "message"; var textNode = document.createTextNode("Hello world!"); element.appendChild(textNode); var anotherTextNode = document.createTextNode("Yippee!"); element.appendChild(anotherTextNode); document.body.appendChild(element);规范化文本节点
DOM文档中存在相邻的同胞文本节点很容易导致混乱,因为分不清文本节点之间的界限。于是催生了一个能够将相邻文本节点合并的方法。
normalize()方法是由Node类型定义的(因而在所有节点类型中都存在)。如果在一个包含多个文本节点的父元素上调用normalize()方法,则会将所有文本节点合并成一个文本节点。
var element = document.createElement("div"); elment.className = "message"; var textNode = document.createTextNode("Hello world!"); element.appendChild(textNode); var anotherTextNode = document.createTextNode("Yippee!"); element.appendChild(anotherTextNode); document.body.appendChild(element); console.log(element.childNodes.length); // 2 element.normalize(); console.log(element.childNodes.length); // 1 console.log(element.firstChild.nodeValue); // "Hello World!Yippee!"
浏览器在解析文档时永远不会创建相邻的文本节点,这种情况只会作为DOM操作的结果出现。
normalize()有时候会导致IE6崩溃,IE7以上修复了此问题。
分割文本节点splitText()方法会将一个文本节点分割成两个。
var element = document.createElement("div"); elment.className = "message"; var textNode = document.createTextNode("Hello world!"); element.appendChild(textNode); document.body.appendChild(element); var newNode = element.firstChild.splitText(5); console.log(element.firstChild.nodeValue); // "Hello" console.log(newNode.nodeValue); // " World!" console.log(element.childNodes.length); // 2Comment类型
注释在DOM中是通过Comment类型来表示的。Comment节点具有以下特征:
nodeType的值为8
nodeName的值为 "#comment"
nodeValue的值是注释的内容
parentNode可能是Dcoment或Element
不支持(没有)子节点
Comment类型与Text类型继承自相同的基类,因此它拥有除splitText()之外的所有字符串操作方法。
var div = document.getElementById("myDiv"); var comment = div.firstChild; console.log(comment.data); // "A comment"
使用document.createComment()并为其传递注释文本也可以创建注释节点
var comment = document.createComment("A comment ");
开发人员很少会创建和访问注释节点,此外浏览器也不会识别位于标签后的注释。如果要访问注释节点,一定要保证它们是位于和之间。
CDATASection类型CDATASection类型只针对基于XML的文档,表示的是CDATA区域。与Comment类似、CDATASection类型继承自Text类型,因此拥有除splitText()之外的所有字符串操作方法。
CDATASection节点具有下列特征:
nodeType的值为4
nodeName的值为"#cdata-section"
nodeValue的值是CDATA区域中的内容
parentNode可能是Document或Element
不支持(没有)子节点
CDATA区域只会出现在XML文档中,因此多数浏览器都会把CDATA区域错误的解析为Comment或Element。
这个例子中div元素应该包含一个CDATASection节点。但四大主流浏览器都不能正确解析。即使对于有效的XHTML页面,浏览器也没有正确的支持嵌入的CDATA区域。
在真正的XML文档中,可以使用document.createCDataSection()来创建CDATA区域。
DocumentType类型
DocumentType类型在Web浏览器中并不常用,仅有 Firefox Safari 和 Opera支持它。
nodeType的值为10
nodeName的值为doctype的名称
nodeValue的值是null
parentNode是Document
不支持(没有)子节点
通常,浏览器中的文档使用的都是HTML或XHTML文档类型,只有name属性是有用的。
console.log(document.doctype.name); // "HTML"
IE不支持DocumentType,因此 document.doctype的值始终都是null
DocumentFragment 类型所有节点类型中,只有DocumentFragment在文档中没有对应的标记。
DOM规定文档片段(document fragment)是一种轻量级的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源。
nodeType的值为11
nodeName的值为"#document-fragment"
nodeValue的值是null
parentNode是null
子节点可以是Element 、ProcessingInstruction 、Comment 、Text、 CDATASection 、EntityReference
虽然不能把文档文段直接添加到文档中,但可以将它作为一个仓库来使用,在里面保存将来可能会添加到文档中的节点。
document.createDocumentFragment() 方法创建文档片段
var fragment = document.createDocumentFragment(); var ul = document.getElementById("myList"); var li = null; // 如果直接向ul添加li元素会导致浏览器反复渲染 // fragment作为一个元素中转的仓库避免了这个问题 for (var i=0; i < 3; i++) { li = document.createElement("li"); li.appendChild(document.createTextNode("Item " + (i+1))); fragment.appendChild(li); } // 这里只会将fragment的所有子节点添加到ul上 // 而fragment本身永远不会成为文档树的一部分 ul.appendChild(fragment);Attr类型
元素的特性在DOM中以Attr类型来表示。在所有浏览器中(包括IE8),都可以访问 Attr类型的构造函数和原型。
nodeType的值为2
nodeName的值就是特性的名称
nodeValue的值就是特性的值
parentNode是null
HTML中不支持(没有)子节点
XML中子节点可以是Text或EntityReference
尽管Attr是节点,但特性却不被认为是DOM文档树的一部分。
Attr对象有三个属性:name value specified。
document.createAttribute()传入特性的名称可以创建新的特性节点。
var attr = document.createAttribute("align"); attr.value = "left"; element.setAttribute(attr); console.log(element.attributes["align"].value); // left console.log(element.getAttributeNode("align").value); // left console.log(element.getAttribute("align")); // leftDOM操作技术 动态脚本
使用元素可以向页面中插入JavaScript代码,一种是通过其src特性包含外部文件,另一种就是用这个元素本身包含代码。
动态加载的JavaScript文件能够立即运行。
// 在执行最后一行代码把
遗憾的是,并没有什么标准方式来探知脚本是否加载完成。
从逻辑上讲,使用行内方式直接插入代码是有效的。在Firefox Safari Chrome Opera中,都可以正常运行,但在IE中,会导致错误。IE将视为一个特殊元素,不允许DOM访问其子节点。不过可以使用元素的text属性来制定JavaScript代码
var script = document.createElement("script"); script.type = "text/javascript"; // 这样IE不支持 script.appendChild( document.createTextNode("function sayHi() { console.log("Hi")}") ); // 可以使用`