本系列内容由ZouStrong整理收录
整理自《JavaScript权威指南(第六版)》,《JavaScript高级程序设计(第三版)》
DOM(文档对象模型)是一个与系统平台和编程语言无关(跨平台,语言中立)的接口(API),程序和脚本可以
通过这个接口动态的访问和修改文档内容、结构、样式
DOM将文档抽象为一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分
每一个window对象都有一个document属性,它指代document对象,代表了脚本所在的文档——这就是DOM的核心对象
注:IE中的所有DOM对象都是以COM对象的形式实现的。这意味着IE中的DOM 对象与原生JavaScript对象的行为或活动特点并不一致
DOM将整个文档映射为一个多层节点树
文档节点是每个文档的根节点(document),它有两个子节点——文档类型声明(document.doctype)和html元素(document.documentElement)
此外还有元素节点、文本节点、注释节点等
通用的Document和ELement类型与HTMLDocument和HTMLELement类型之间没有严格的区别,只不过前者针对HTML或者XML文档,后者只针对HTML文档,通常都是使用通用的Document和ELement类型
总共有12种节点类型,这些类型都继承自一个基类型——Node类型
JavaScript中的所有节点类型都继承自Node类型,因此所有节点类型都共享着相同的基本属性和方法
每个节点都有一个nodeType属性,用于表明节点的类型(返回一个常量表示的整数值,但是使用常量值会在低版本IE下报错,所以要使用整数值)
通过比较数值,很容易确定节点类型
if (someNode.nodeType == 1){
alert("这是一个元素节点");
}
该属性返回节点名称(始终都是大写),通常只对元素节点有用,返回的就是这个元素的标签名(其他节点则返回null),因此使用这个值以前,最好先检测一下节点的类型
if (someNode.nodeType == 1){
alert(someNode.nodeName);
}
该属性返回节点值,通常只对文本节点或注释节点有用,使用这个值以前,最好先检测一下节点的类型
if (someNode.nodeType == 3){
alert(someNode.nodeValue);
}
对于文本和注释节点,nodeValue属性值等于它们的data属性值
返回父节点(对于document节点,则返回null)
该属性返回一个NodeList对象(类数组对象),该对象保存着元素的所有子节点的实时表示
可以通过length属性返回子节点的个数(hasChildNodes()方法在length属性大于0时返回true)
通过方括号语法(常用)或者item()方法来访问这些节点
注:这个属性返回所有类型的子节点,包括了元素节点、文本节点甚至空格,回车
遍历子节点,那么一定不要忘记浏览器间的这一差别。这意味着在执行某项操作以前,通常都要先检查一下nodeTpye属性
for (var i=0, len=ele.childNodes.length; i < len; i++){
if (ele.childNodes[i].nodeType == 1){
//执行某些操作
}
}
注:NodeList对象的独特之处在于,它实际上是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反映在NodeList对象中。我们常说,NodeList是有生命、有呼吸的对象,而不是在我们第一次访问它们的某个瞬间拍摄下来的一张快照,而这也正是操作DOM比较耗内存的原因
返回childNodes列表中的第一个(最后一个)节点,在只有一个子节点的情况下,firstChild 和 lastChild指向同一个节点;如果没有子节点,那么firstChild和lastChild的值均为null
someNode.firstChild==someNode.childNodes[0]
someNode.lastChild==someNode.childNodes[someNode.childNodes.length-1]
返回节点的下一个(上一个)兄弟节点,最后一个节点的nextSibling属性的值为null,第一个节点的previousSibling属性值为null
注:nextSibling属性和previousSibling属性返回的节点中,都把空格计算在内
所有节点都有的最后一个属性——ownerDocument属性,它返回整个文档的文档节点(document)
除了这些属性之外,Node类型还有以下方法
检测是否包含子节点
注:也可查询childNodes列表的length属性来达到同样效果
用于向节点末尾添加一个节点,返回新增的节点
var returnedNode = someNode.appendChild(newNode);
注:如果传入到appendChild()中的节点已经是文档的一部分了,那结果就是将该节点从原来的位置转移到新位置
接受两个参数:要插入的节点和作为参照的节点,返回插入的节点。如果参照节点是null,则insertBefore()与appendChild()执行相同的操作
注:insertBefore()方法用在父节点上
接受两个参数:要插入的节点和要替换的节点,返回被替换的节点
这个方法接受一个参数,即要移除的节点,返回被移除的节点
注:被移除的节点仍然为文档所有,只不过在文档中已经没有了自己的位置,后续可以继续插入等操作
注:前面四个方法操作的都是某个节点的子节点,也就是说,要使用这几个方法必须先取得父节点(使用parentNode属性)
该方法用于复制节点,返回复制的节点
该方法接受一个布尔值参数表示是否执行深度复制,在参数为true的情况下,执行深复制,也就是复制节点及其整个子节点树;在参数为false的情况下,执行浅复制,即只复制节点本身
返回的节点副本是一个“孤儿”,除非通过appendChild()、insertBefore()等将它添加到文档中
cloneNode()方法不会复制添加到DOM节点中的JavaScript属性,例如事件处理程序等
注:IE存在一个bug,即它会复制事件处理程序,所以在复制之前最好先移除事件处理程序
处理文档树中的文本节点
Document类型表示整个文档(XML文档,HTML文档)
在浏览器中,document对象是HTMLDocument类型(继承
自Document类型)的一个实例,表示整个 HTML 页面。而且,document对象是window对象的一个属性,因此可以将其作为全局对象来访问
Document节点具有下列特征
最常见的应用还是HTMLDocument的实例——document对象(通过它,不仅可以取得与页面有关的信息,而且还能操作页面的外观及其底层结构)
DOM标准规定Document节点的子节点可以是DocumentType、Element、ProcessingInstruction或Comment
有两个内置的访问其子节点的快捷方式
即
var body = document.documentElement;
document对象还有一个body属性,指向body元素
var body = document.body;
所有浏览器都支持这两个属性
Document另一个可能的子节点是DocumentType(文档类型声明),可以通过 document.doctype属性来获取
var doctype = document.doctype;
但是document.doctype的支持差别很大(IE8及其之前浏览器返回null)
最后,ECMAScript5新增了head属性,返回head元素
var head = document.head; //IE8及其以下不支持
document对象还有一些属性提供了网页的一些信息
其中第一个属性就是title属性,返回title元素中的文本,而且它是可读可写的,因此可以通过它来设置网页的title
var originalTitle = document.title;
document.title = "New page title";
还有3 个属性都与网页请求有关,它们是URL、domain和referrer
URL属性中包含页面完整的url,相当于location.href,但却是只读的
var url =document.URL;
domain属性中只包含页面的域名,通过它可以返回或设置页面的域名(但不会显示在地址栏中)
var domain =document.domain;
referrer属性返回跳转到当前页面的那个页面的URL,或者在没有来源页面的情况下,referrer属性包含空字符串
var referrer =document.referrer;
在这3个属性中,只有domain是可以设置的。但出于安全考虑,不可以给 domain随意设置值——不能将这个属性设置为URL中不包含的域
//假设页面来自zou.strong.com域
document.domain = "strong.com"; // 成功!
document.domain = "strongger.com"; // 出错!
当页面中包含来自其他子域的框架或内嵌框架时,能够设document.domain 就显的很有用
由于跨域安全限制,来自不同子域的页面无法通过JavaScript 通信。而通过将每个页面的document.domain设置为相同的值,这些页面就可以互相访问对方包含的JavaScript对象了
假设有一个页面加载自www.strong.com,包含一个内嵌框架,框架内的页面加载自zou.strong.com,由于document.domain不同,两个页面之间无法相互访问对方的JavaScript对象。但如果将这两个页面的document.domain值都设置为"strong.com",它们之间就可以通信了
此外,浏览器对domain属性还有一个限制,即如果域名一开始是“松散的”,那么不能将它再设置为“紧绷的”
//假设页面来自zou.strong.com域
document.domain = "strong.com"; // 成功!
document.domain = "zsz.strong.com"; // 出错!
document对象还有一些特殊的属性,以下这些属性返回一个HTMLCollection对象
除了上面的属性可以获取元素之外,更通用的方式就是使用document对象的几个方法来完成
Document类型为此提供了两个方法
getElementById()方法
接收一个参数,元素ID,返回带有相应ID的元素(或者没有的话,则返回 null)
参数ID必须与页面中元素的id特性(attribute)严格匹配,包括大小写
如果页面中多个元素的ID值相同(当然,这一般是不允许的), getElementById()只返回文档中第一次出现的元素
IE7及之前版本有BUG,name特性与给定ID参数匹配的表单元素也会被该方法返回。如果有哪个表单元素的 name 特性等于指定的 ID,而且该元素在文档中位于带有给定 ID 的元素前面,那么 IE 就会返回那个表单元素
<input type="text" name="myElement">
<div id="myElement"></div>
在IE7中调用 document.getElementById("myElement "),结果会返回input元素;而在其他所有浏览器中,都会返回对
getElementsByTagName()方法
接受一个参数,元素名(不区分大小写),返回包含零或多个元素的HTMLCollection 对象,作为一个“动态”集合,该对象与 NodeList 非常类似
var images = document.getElementsByTagName("img");
可以使用方括号语法或item()方法来访问HTMLCollection对象中的项,通过length属性获得元素的个数
此外,HTMLCollection对象还有一个namedItem()方法,使用这个方法可以通过元素的name特性取得集合中的项
<img src="myimage.gif" name="myImage">
var images = document.getElementsByTagName("img");
var myImage = images.namedItem("myImage");
第三个方法
getElementsByName()方法
接受一个参数,元素的name特性,返回包含零或多个元素的HTMLCollection 对象
document对象具有向文档中写入内容的能力,有以下四个方法write()、writeln()、open()和close()(他们在DOM之前就出现了,现在已经很少使用了)
write()和writeln()会将参数字符串拼接起来,然后插入到文档中调用他的脚本的位置(后者会多插入一个换行符\n),当脚本执行完毕,浏览器解析生成的输出并显示它
只有解析文档时,才能使用document.write()输出内容,这是因为这些脚本的执行也是文档解析流的一部分,如果document.write()被放在一个函数中,而函数是作为某个事件处理程序调用的,他可能就会重写整个文档
window.onload = function(){
document.write("Hello world!"); //重写整个文档
};
在设置了defer或async属性的脚本中,不允许使用document.write()
方法 open()和 close()分别用于打开和关闭网页的输出流。如果是在页面加载期间使用 write()
或 writeln()方法,则不需要用到这两个方法
Element类型表示XML或HTML元素,可以方便的获取元素名,子节点和特性
Element节点具有下列特征
使用nodeName或者tagName属性,可以获取大写的标签名(后者的意图更加清晰)
注意的是,在HTML中,标签名始终都以全部大写表示;而在 XML(有时候也包括 XHTML)中,标签名则始终会与源代码中的保持一致
因此,最好先转换一下再比较
if (element.tagName == "div"){
//不能这样比较,很容易出错!
}
if (element.tagName.toLowerCase() == "div"){
//这样最好(适用于任何文档)
}
所有HTML元素都由HTMLElement类型或者其子类型(HTMLDivElement、HTMLParagraphElement.....)表示
HTMLElement类型拥有一些特有的属性,因此每个html元素都继承了下列标准属性(他们都是可读、可写的)
dir属性——返回元素的语言方向
div.id; //获取值
div.id = "strong"; //设置值
所有标准特性(非自定义的)都可以通过元素对象属性的形式访问到,访问不存在的特性会返回undefined
元素对象属性可以方便的获取或设置元素的标准特性,但是对于自定义特性却无能为力
因此需要使用更加通用的方法,可以针对任何特性使用
该方法接受一个特性名,返回对应的特性值,不存在的话,返回null
var div = document.getElementById("myDiv");
alert(div.getAttribute("id"));
alert(div.getAttribute("class"));
alert(div.getAttribute("my_special_attribute")); //取得自定义特性
//根据 HTML5 规范,自定义特性应该加上data-前缀以便验证
注意,传递给 getAttribute()的特性名与实际的特性名相同。因此要想得到 class 特性值,应该传入"class"而不是"className",后者只有在通过对象属性访问特性时才用
有两类特殊的特性,它们虽然有对应的属性名,但属性的值与通过 getAttribute()返回的值并不相同
由于存在这些差别,在通过JavaScript以编程方式操作DOM时,不经常使用getAttribute(),而是只使用对象的属性;只有在取得自定义特性值的情况下,才会使用getAttribute()
该方法接受两个参数:要设置的特性名和值(如果特性已经存在,则会以指定的值替换现有的值)
div.setAttribute("id", "strong");
div.setAttribute("CLASS", "strong");
通过这个方法设置的特性名会被统一转换为小写形式,即"CLASS"最终会变成"class"
因为所有特性都是属性,所以直接给属性赋值可以设置特性的值
div.id = "someOtherId";
但是为元素添加一个自定义的属性,则不会自动成为元素的特性
div.mycolor = "red";
alert(div.getAttribute("mycolor")); //null( IE 除外)
在 IE7 及以前版本中, setAttribute()设置class 和 style 特性,无效
该方法接受一个特性名,用于彻底删除该特性
div.removeAttribute("class");
IE6及以前版本不支持
attributes属性是元素节点特有的属性,它返回一个该元素所有特性组成的NamedNodeMap对象(与 NodeList 类似,也是一个“动态”的集合)
这个对象拥有以下属性和方法
返回的节点的nodeName就是特性的名称, 而节点的nodeValue就是特性的值
//取得元素的 id 特性
var id = element.attributes.getNamedItem("id").nodeValue;
//简写方式
var id = element.attributes["id"].nodeValue;
removeNamedItem()方法与在元素上调用 removeAttribute()方法的效果相同——直接删
除具有给定名称的特性
setNamedItem()方法可以为元素添加一个新特性
element.attributes.setNamedItem(newAttr);
attributes属性的方法不够方便(遍历元素的特性倒是可以),更常用getAttribute()、 removeAttribute()和 setAttribute()方法
document.createElement()方法可以创建新元素
接受一个参数,要创建的元素名
var div = document.createElement("div");
此时,该元素虽然还没有被插入到文档中,但是却拥有了ownerDocument 属性
此后,就可以操作元素的特性,为它添加更多子节点,以及执行其他操作
div.id = "myNewDiv";
div.className = "box";
要把新元素添加到文档树,可以使用 appendChild()、 insertBefore()或 replaceChild()方法
document.body.appendChild(div);
一旦将元素添加到文档树中,浏览器就会立即呈现该元素。此后,对这个元素所作的任何修改都会实时反映在浏览器中
元 素 的childNodes属性中包含了它的所有子节点,这些子节点有可能是元素、文本节点(包括空白符)、注等(不同的浏览器处理起来是不同的,因此length属性可能不一样)
这意味着在执行某项操作以前,要先检查一下nodeTpye属性
var len = element.childNodes.length;
for (var i=0;i < len;++){
if (element.childNodes[i].nodeType == 1){
//执行某些操作
}
}
此外元素节点上也支持getElementsByTagName()方法,结果只会返回当前元素的后代元素
Text类型表示文本节点,包含的是纯文本内容
可以通过nodeValue属性或data属性访问和设置Text节点中包含的文本(两者作用完全相同)
<div id="s">文本内容</div>
alert(window.s.firstChild.data); //"文本内容"
alert(window.s.firstChild.nodeValue); //"文本内容"
window.s.firstChild.data = "新文本内容"; //改变元素内的文本
这里需要理解的就是,window.s.firstChild返回的只是Text对象,要想取得字符串文本值,还要使用nodeValue属性或data属性
使用下列方法可以操作节点对象中的文本
此外,节点对象还有一个length属性,保存着节点中字符的数目(与它的nodeValue.length和data.length相同)
使用document.createTextNode()创建文本节点,这个方法接受一个参数——要插入节点中的文本
var tNode = document.createTextNode("123");
在创建新文本节点的同时,也会为其设置ownerDocument 属性
默认情况下,元素最多只能有一个文本节点
<!-- 没有内容,也就没有文本节点 -->
<div></div>
<!-- 有空格,因而有一个文本节点 -->
<div> </div>
<!-- 有内容,因而有一个文本节点 -->
<div>Hello World!</div>
不过,在某些情况下也可能包含多个文本子节点(浏览器在解析文档时永远不会创建相邻的文本节点。这种情况只会在执行DOM操作时出现)
var tNode1 = document.createTextNode("12");
element.appendChild(tNode1);
var tNode2 = document.createTextNode("34");
element.appendChild(tNode2);
上面产生的就是两个文本节点
alert(element.childNodes[0].data); //"12"
alert(element.childNodes[1].data); //"34"
但是,对于经常操作DOM的我们来说,我们并不希望区分这两个文本节点,于是一个能够将相邻文本节点合并的方法应运而生
normalize()方法,是由Node类型定义的(因而在所有节点类型中都存在)如果在一个包含两个或多个文本节点的父元素上调用 normalize()方法,则会将所有文本节点合并成一个节点
element.normalize();
alert(element.childNodes[0].data); //"1234"
normalize()方法会导致IE6的某些版本崩溃
splitText()方法与normalize()作用相反,它会将一个文本节点分成两个文本节点
接受一个数字参数,表示分隔的位置(该方法实在文本对象上调用的,不是在父元素上)
element.normalize();
alert(element.childNodes[0].data); //"1234"
element.firstChild.splitText(2);
alert(element.childNodes[0].data); //"12"
alert(element.childNodes[1].data); //"34"
尽管它们也是节点,但特性却不被认为是 DOM 文档树的一部分。开发人员最常使用的是 getAttribute()、 setAttribute()和 remveAttribute()方法,很少直接引用特性节点
......
......
......
在所有节点类型中,只有DocumentFragment在文档中没有对应的标记
DOM 规定文档片段(document fragment)是一种“轻量级”的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源
虽然不能把文档片段直接添加到文档中,但可以将它作为一个“仓库”来使用,即可以在里面保存将来可能会添加到文档中的节点
首先,创建文档片段
var fragment = document.createDocumentFragment();
文档片段继承了Node的所有方法
如果将文档中的节点添加到文档片段中,就会从文档树中移除该节点;可以通过 appendChild()或 insertBefore()将文档片段中内容添加到文档中。在将文档片段作为参数传递给这两个方法时,实际上只会将文档片段的所有子节点添加到相应位置上;文档片段本身永远不会成为文档树的一部分
<ul id="myList"></ul>
假设我们想为这个
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);