一、 DOM
DOM
(文档对象模型)是针对HTML
和XML
文档的一个API
(应用程序编程接口)。DOM
描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。DOM
脱胎于Netscape 及微软公司创始的DHTML
(动态HTML
),但现在它已经成为表现和操作页面标记的真正的跨平台、语言中立的方式。
Node类型
DOM1
级定义了一个Node
接口,该接口将由DOM
中的所有节点类型实现。每个节点都有一个nodeType
属性,用于表明节点的类型。节点类型由在Node 类型中定义的下列12 个数值常量来表示,任何节点类型必居其一:
- 节点关系
每个节点都有一个childNodes
属性,其中保存着一个NodeList
对象。NodeList
是一种类数组对象(可以通过下标访问,有length
属性,但它并不是Array
的实例),用于保存一组有序的节点,可以通过位置(索引 / 方括号写法)来访问这些节点。NodeList
对象的独特之处在于,它实际上是基于DOM
结构动态执行查询的结果,因此DOM
结构的变化能够自动反映在NodeList
对象中。我们常说,NodeList
是有生命、有呼吸的对象,而不是在我们第一次访问它们的某个瞬间拍摄下来的一张快照。
如何访问保存在NodeList
中的节点——可以通过方括号,也可以使用item()
方法。
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var count = someNode.childNodes.length;
使用Array.prototype.slice()
方法可以将NodeList
对象转换为数。
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0);
-
节点关系的引用属性
-
parentNode
:该属性指向文档树中的父节点 -
previousSibling
:该属性指向文档书中的前一个兄弟节点,第一个节点的previousSibling
属性值为null
-
nextSibling
:该属性指向文档书中的前一个兄弟节点,最后一个节点的nextSibling
属性值为null
- 父节点的
firstChild
和lastChild
属性分别指向其childNodes
列表中的第一个和最后一个节点 中,someNode.firstChild
的值始终等于someNode.childNodes[0]
, 而someNode.lastChild
的值始终等于someNode.childNodes [someNode.childNodes.length-1]
。
-
-
操作子节点
-
appendChild()
:用于向childNodes
列表的末尾添加一个节点。添加节点后,childNodes
的新增节点、父节点及以前的最后一个子节点的关系指针都会相应地得到更新。更新完成后,appendChild()
返回新增的节点。如果传入到appendChild()
中的节点已经是文档的一部分了,那么该节点就会成为父节点的最后一个子节点。 -
insertBefore()
:把节点放在childNodes
列表中某个特定的位置上,这个方法接受两个参数:要插入的节点和作为参照的节点。插入节点后,被插入的节点会变成参照节点的前一个同胞节点(previousSibling
),同时被方法返回。如果参照节点是null
,则insertBefore()
与appendChild()
执行相同的操作。 -
replaceChild()
:这个方法接受两个参数,要插入的节点和要替换的节点。要替换的节点将由这个方法返回并从文档树中被移除,同时由要插入的节点占据其位置。在使用replaceChild()
插入一个节点时,该节点的所有关系指针都会从被它替换的节点复制过来。尽管从技术上讲,被替换的节点仍然还在文档中,但它在文档中已经没有了自己的位置。 -
removeChild()
:这个方法接受一个参数,即要移除的节点。被移除的节点将成为方法的返回值。
-
-
操作节点的其他方法
-
cloneNode()
,用于创建调用这个方法的节点的一个完全相同的副本。cloneNode()
方法接受一个布尔值参数,表示是否执行深复制。在参数为true
的情况下,执行深复制,也就是复制节点及其整个子节点树;在参数为false
的情况下,执行浅复制,即只复制节点本身。复制后返回的节点副本属于文档所有,但并没有为它指定父节点。因此,这个节点副本就成为了一个“孤儿”,除非通过appendChild()
、insertBefore()
或replaceChild()
将它添加到文档中。 -
normalize()
,由于解析器的实现或DOM
操作等原因,可能会出现文本节点不包含文本,或者接连出现两个文本节点的情况。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到了空文本节点,则删除它;如果找到相邻的文本节点,则将它们合并为一个文本节点。
-
var deepList = myList.cloneNode(true);
alert(deepList.childNodes.length); //3(IE < 9)或7(其他浏览器)
var shallowList = myList.cloneNode(false);
alert(shallowList.childNodes.length); //0
cloneNode()
方法不会复制添加到DOM
节点中的JavaScript
属性,例如事件处理程序等。这个方法只复制特性、(在明确指定的情况下也复制)子节点,其他一切都不会复制。IE 在此存在一个bug,即它会复制事件处理程序,所以我们建议在复制之前最好先移除事件处理程序。
二、 Document 类型
document
对象:在浏览器中,document
对象是HTMLDocument
(继承自Document
类型)的一个实例,表示整个HTML
页面。而且,document
对象是window
对象的一个属性,因此可以将其作为全局对象来访问。通过这个文档对象,不仅可以取得与页面有关的信息,而且还能操作页面的外观及其底层结构。Document
节点具有下列特征: nodeType
的值为9;nodeName
的值为"#document"
;nodeValue
的值为null
; parentNode
的值为null
;
document
对象的属性
var html = document.documentElement; //取得对<html>的引用
var body = document.body; //取得对<body>的引用
var doctype = document.doctype; //取得对<DOCTYPE>文档类型的引用
//取得文档标题
var originalTitle = document.title;
//设置文档标题
document.title = "New page title";
//取得完整的URL,不可设置
var url = document.URL;
//取得域名,可设置,但是不能随便设置
var domain = document.domain;
//假设页面来自p2p.wrox.com 域
document.domain = "wrox.com"; // 成功
document.domain = "nczonline.net"; // 出错! 只能省略域名
//取得来源页面的URL,不可设置
var referrer = document.referrer;
查找元素
1、getElementById()
:接收一个参数:要取得的元素的ID
。如果找到相应的元素则返回该元素,如果不存在带有相应ID 的元素,则返回null
。注意,这里的ID
必须与页面中元素的id
特性(attribute
)严格匹配,包括大小写。IE8 及较低版本不区分ID
的大小写,因此"myDiv"
和"mydiv"
会被当作相同的元素ID
。如果页面中多个元素的ID 值相同,getElementById()
只返回文档中第一次出现的元素。
<input type="text" name="myElement" value="Text field">
<div id="myElement">A div</div>
基于这段HTML 代码,在IE7 中调用document.getElementById("myElement ")
,结果会返回<input>
元素;而在其他所有浏览器中,都会返回对<div>
元素的引用。为了避免IE中存在的这个问题,最好的办法是不让表单(<input>
、<textarea>
、<button>
及<select>
)字段的name
特性与其他元素的ID
相同。
2、getElementsByTagName()
。这个方法接受一个参数,即要取得元素的标签名,返回一个HTMLCollection
对象,作为一个“动态”集合,返回所有的标签元素组成的类数组。HTMLCollection
对象还有一个方法,叫做namedItem()
,使用这个方法可以通过元素的name
特性取得集合中的项。例如,
<img src="myimage.gif" name="myImage">
// 那么就可以通过如下方式从images几个中取得对应name的<img>元素:
var images = document.getElementsByTagName("img");
var myImage = images.namedItem("myImage");
// HTMLCollection 还支持按名称访问项
var myImage = images["myImage"];
在通过元素调用这个方法时,除了搜索起点是当前元素之外,其他方面都跟通过
document
调用这个方法相同,因此结果只会返回当前元素的后代。
var ul = document.getElementById("myList");
var items = ul.getElementsByTagName("li");
3、getElementsByClassName()
:这个方法接受一个参数,即要取得元素的类名,返回一个HTMLCollection
对象,作为一个“动态”集合,返回所有拥有该类的元素组成的类数组。
4、为访问文档常用的部分提供了快捷方式:
-
document.anchors
,包含文档中所有带name
特性的<a>
元素; -
document.forms
,包含文档中所有的<form>
元素 -
document.images
,包含文档中所有的<img>
元素 -
document.links
,包含文档中所有带href 特性的<a>
元素。
这些特殊集合始终都可以通过HTMLDocument
对象访问到,而且,与HTMLCollection
对象类似,集合中的项也会随着当前文档内容的更新而更新。
文档写入
write()
和writeln()
方法都接受一个字符串参数,即要写入到输出流中的文本。write()
会原样写入,而writeln()
则会在字符串的末尾添加一个换行符(\n
)。在页面被加载的过程中,可以使用这两个方法向页面中动态地加入内容。方法open()
和close()
分别用于打开和关闭网页的输出流。
<html>
<head>
<title>document.write() Example</title>
</head>
<body>
<p>The current date and time is:
<script type="text/javascript">
document.write("<strong>" + (new Date()).toString() + "</strong>");
</script>
</p>
</body>
</html>
这样做会创建一个DOM
元素,而且可以在将来访问该元素。通过write()
和writeln()
输出的任何HTML
代码都将如此处理。
还可以使用write()
和writeln()
方法动态地包含外部资源,例如JavaScript
文件等。在包含JavaScript
文件时,必须注意不能像下面的例子那样直接包含字符串"</script>"
,因为这会导致该字符串被解释为脚本块的结束,它后面的代码将无法执行。应该使用转义符做处理"<\/script>"
window.onload
事件处理程序,等到页面完全加载之后延迟执行函数。函数执行之后,调用document.write()
会重写整个页面内容。
三、Element 类型
Element
类型用于表现XML
或HTML
元素,提供了对元素标签名、子节点及特性的访问。Element
节点具有以下特征:nodeType
的值为1;nodeName
的值为元素的标签名;nodeValue
的值为null
;
要访问元素的标签名,可以使用nodeName
属性,也可以使用tagName
属性;这两个属性会返回相同的值。
所有HTML
元素都由HTMLElement
类型表示,不是直接通过这个类型,也是通过它的子类型来表示。HTMLElement
类型直接继承自Element
并添加了一些属性。添加的这些属性分别对应于每个HTML元素中都存在的下列标准特性。
id,元素在文档中的唯一标识符
title,有关元素的附加说明信息
lang,元素内容的语言代码,很少使用。
dir,语言的方向,值有ltr从左到右 rtl从右到做 ,很少使用
className,与元素的class 特性对应,即为元素指定的CSS类。
没有将这个属性命名为class,是因为class 是ECMAScript 的保留字。
通过className访问而不是class,**但是在使用操作元素属性的方法时需要使用class,与html上的属性保持一致**
<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>
操作元素属性
每个元素都有一或多个特性,这些特性的用途是给出相应元素或其内容的附加信息。操作特性的DOM
方法主要有三个,分别是getAttribute()
、setAttribute()
和removeAttribute()
,接受特性字符串作为第一个参数。setAttribute()
可以传第二个参数作为设置的新值。特性的名称是不区分大小写的,即"ID"
和"id"
代表的都是同一个特性。另外也要注意,根据HTML5
规范,自定义特性应该加上data-
前缀以便验证。
注意,传递给
getAttribute()
的特性名与实际的特性名相同。因此要想得到class
特性值,应该传入"class"
而不是"className"
,后者只有在通过对象属性访问特性时才用。如果给定名称的特性不存在,getAttribute()
返回null
。
1、 getAttribute()
获取属性
任何元素的所有特性,也都可以通过DOM
元素本身的属性来访问。不过,只有公认的(非自定义的)特性才会以属性的形式添加到DOM
对象中。自定义属性只能通过getAttribute()
来获得
<div id="myDiv" align="left" my_special_attribute="hello!"></div>
alert(div.id); //"myDiv"
alert(div.my_special_attribute); //undefined(IE 除外)
alert(div.align); //"left"
有两类特殊的特性,它们虽然有对应的属性名,但属性的值与通过getAttribute()
返回的值并不相同。
-
第一类特性就是style,用于通过CSS 为元素指定样式。
- 在通过
getAttribute()
访问时,返回的style
特性值中包含的是CSS
文本 - 而通过属性来访问它则会返回一个对象。由于
style
属性是用于以编程方式访问元素样式的(本章后面讨论),因此并没有直接映射到style
特性。
- 在通过
-
第二类与众不同的特性是
onclick
这样的事件处理程序。- 如果通过
getAttribute()
访问,则会返回相应代码的字符串。 - 在访问
onclick
属性时,则会返回一个JavaScript
函数(如果未在元素中指定相应特性,则返回null
)。这是因为onclick
及其他事件处理程序属性本身就应该被赋予函数值。
- 如果通过
由于存在这些差别,在通过
JavaScript
以编程方式操作DOM
时,开发人员经常不使用getAttribute()
,而是只使用对象的属性。只有在取得自定义特性值的情况下,才会使用getAttribute()
方法。
2、 setAttribute()
获取属性
setAttribute()
,这个方法接受两个参数:要设置的特性名和值。如果特性已经存在,setAttribute()
会以指定的值替换现有的值;如果特性不存在,setAttribute()
则创建该属性并设置相应的值。通过setAttribute()
方法既可以操作HTML
特性也可以操作自定义特性。通过这个方法设置的特性名会被统一转换为小写形式,即"ID"最终会变成"id"。如果是使用这样的方式oDiv.myColor="div1"
的方式设置的自定义属性,用getAttribute()
是访问不到的。
3、 removeAttribute()
删除属性
接受一个参数,即删除的属性key,这个方法用于彻底删除元素的特性。调用这个方法不仅会清除特性的值,而且也会从元素中完全删除特性,oDiv.removeAttribute("class");
创建元素
1、 document.createElement()
创建元素
这个方法只接受一个参数,即要创建元素的标签名(IE中可以传入完整的HTML标签字符串)。这个标签名在HTML
文档中不区分大小写,而在XML
(包括XHTML
)文档中,则是区分大小写的。
var oDiv = document.createElement("div");
2、 增加元素
-
appendChild()
:ParentElement.appendChild(element);
在父元素末尾添加一个子元素 -
insertBefore()
:ParentElement.insertBefore(newElement,refElement);
指定的已有子节点之前插入新的子节点。
3、删除元素
-
removeChild()
:ParentElement.removeChild(elelment);
在父元素中移除某个子元素
4、改变元素
-
replaceChild()
:ParentElement.replaceChild(newElement,oldElement);
在父元素中替换某个子元素
元素的子节点
元素的childNodes
属性中包含了它的所有子节点,这些子节点有可能是元素、文本节点、注释或处理指令。IE会返回所有的子元素节点,而其他浏览器则返回所有的子节点,这意味着在执行某项操作以前,通常都要先检查一下nodeTpye
属性
for (var i=0, len=element.childNodes.length; i < len; i++){
if (element.childNodes[i].nodeType == 1){
//对子元素节点执行某些操作
}
}
四、Text 类型
每个可以包含内容的元素最多只能有一个文本节点,而且必须确实有内容存在。(只有空格也算是有内容)
-
nodeType
的值为3
; -
nodeName
的值为"#text"
; -
nodeValue
的值为节点所包含的文本;
如果这个文本节点当前存在于文档树中,那么修改文本节点的结果就会立即得到反映。另外,在修改文本节点时还要注意,此时的字符串会经过HTML
(或XML
,取决于文档类型)编码。换句话说,小于号、大于号或引号都会像下面的例子一样被转义。
Some <strong>other</strong> message
转义为
Some <strong>other</strong> message
创建文本节点
document.createTextNode()
: 创建新文本节点,这个方法接受一个参数——要插入节点中的文本。与设置已有文本节点的值一样,作为参数的文本也将按照HTML
或XML
的格式进行编码。创建的文本节点,可以使用元素的appendChild()
将其添加到元素中,在没有添加进DOM树之前,都是属于孤儿节点,文档碎片。
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
一般情况下,每个元素只有一个文本子节点。不过,在某些情况下也可能包含多个文本子节点,比如使用appendChild()
方法多次添加文本节点,那么这两个节点中的文本就会连起来显示,中间不会有空格。如果在一个包含两个或多个文本节点的父元素上调用normalize()
方法,则会将所有文本节点合并成一个节点,结果节点的nodeValue
等于将合并前每个文本节点的nodeValue
值拼接起来的值。
浏览器在解析文档时永远不会创建相邻的文本节点。这种情况只会作为执行DOM操作的结果出现。
五、DOM操作技术
DOM
操作往往是JavaScript
程序中开销最大的部分,而因访问NodeList
导致的问题最多。NodeList
对象都是“动态的”,这就意味着每次访问NodeList
对象,都会运行一次查询。有鉴于此,最好的办法就是尽量减少DOM
操作。
动态脚本
指的是在页面加载时不存在,但将来的某一时刻通过修改DOM动态添加的脚本。
1、插入外部文件
function loadScript(url){
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
document.body.appendChild(script); // 在这行代码执行之前都不会下载文件
}
loadScript("client.js"); // 方法调用时 动态添加script标签 并执行脚本
2、行内方式,也是添加script标签 ,但是脚本代码吧不是从外部引入,而是本地添加进去。所有写在<script></script>标签对中的代码都算是script元素的文本节点
var script = document.createElement("script");
script.type = "text/javascript";
script.text = "function sayHi(){alert('hi');}";
document.body.appendChild(script);
Safari 3.0 之前的版本不能正确地支持text
属性,可以使用创建文本节点的方式解决。
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');}");
动态样式
能够把CSS 样式包含到HTML 页面中的元素有两个。其中,<link>
元素用于包含来自外部的文件,而<style>
元素用于指定嵌入的样式。
同理。操作DOM
代码添加标签可以很容易地动态创建出作用CSS样式的元素:
1、外部引入link
function loadStyles(url){
var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = url;
var head = document.getElementsByTagName("head")[0];
head.appendChild(link);
}
loadStyles("myStyle.css");
加载外部样式文件的过程是异步的,也就是加载样式与执行JavaScript
代码的过程没有固定的次序。
2、内部style
这种方式会实时地向页面中添加样式,因此能够马上看到变化。
function loadStyleString(css){
var style = document.createElement("style");
style.type = "text/css";
try{
style.appendChild(document.createTextNode(css));
} catch (ex){
style.styleSheet.cssText = css;
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);
}
loadStyleString("body{background-color:red}");
事实上,IE 此时抛出的错误(将<script>
、<style>
视为一个特殊的元素,不允许DOM
访问其子节点。)。解决IE 中这个问题的办法,就是访问元素的styleSheet
属性,该属性又有一个cssText
属性,可以接受CSS
代码
操作表格
为了方便构建表格,HTML DOM
还为<table>
、<tbody>
和<tr>
元素添加了一些属性和方法。
为<table>
元素添加的属性和方法如下。
-
caption
:保存着对<caption>
元素(如果有)的指针。 -
tBodies
:是一个<tbody>
元素的HTMLCollection
。 -
tFoot
:保存着对<tfoot>
元素(如果有)的指针。 -
tHead
:保存着对<thead>
元素(如果有)的指针。 -
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
集合中的指定位置插入一个单元格,返回对新插入单元格的引用。
使用NodeList
理解NodeList
及其“近亲”NamedNodeMap
和HTMLCollection
,是从整体上透彻理解DOM
的关键所在。这三个集合都是“动态的”;换句话说,每当文档结构发生变化时,它们都会得到更新。因此,它们始终都会保存着最新、最准确的信息。从本质上说,所有NodeList
对象都是在访问DOM
文档时实时运行的查询。
如果想要迭代一个NodeList
,最好是使用length
属性初始化第二个变量,然后将迭代器与该变量进行比较。因为NodeList
是动态的,NodeList.length
也是动态的,如果在迭代的过程对这个NodeList
有添加Node
的操作,将会无限迭代下去,所以可以使用变量暂存length:var len = NodeList.length
一般来说,应该尽量减少访问NodeList
的次数。因为每次访问NodeList
,都会运行一次基于文档的查询。所以,可以考虑将从NodeList
中取得的值缓存起来。