DOM对象是针对HTML和XML文档的一个API。DOM描绘了一个层次化的节点树,允许开发人员添加修改页面的一部分。
节点层次
DOM可以将HTML和XML文档描绘成一个多层节点组成的结构。节点分为不同的类型,每种类型分别表示文档中不同的信息或标记。每个节点都拥有各自的特点、数据和方法,与其它节点也可能存在某种关系。节点之间的关系构成了层次,所有页面的标记则表现为以特定节点为跟节点的树形结构。
1、Node类型
DOM1级定义了一个Node接口,该接口将由DOM中的所有节点类型实现。这个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)。
通过比较上面这些常量,可以很容易的确定节点的类型。
if (someNode.nodeType == 1){ //在所有浏览器中有效
alert("Node is an element.");
}
nodeName和nodeValue属性:
要了解节点的具体信息,可以使用nodeName和nodeValue属性。这两个属性完全取决于节点的类型。
检查节点类型:
if (document.getElementById("first").nodeType == 1){
value = document.getElementById("first").nodeName;
console.log(value);// DIV
}
对于元素节点,nodeName保存的是元素的标签名,而nodeValue始终为null。
节点关系:
文档中的所有节点都存在这样或那样的关系。节点之间的关系就好像家族的家谱树。例如,在HTML中body是html元素的子元素,反过来说,html元素就是body元素的父元素。而head元素,则可以看成是body元素的同胞元素。每个节点都有一个childNodes属性,保存着一个NodeList对象,注意,NodeList对象并不是Array的实例,它是对DOM动态查询的结果。
使用方法:
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var count = someNode.childNodes.length;
使用下列方法可以将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列表中的所有节点都有相同的父节点。另外,包含在childNodes列表中的所有节点都是同胞节点,可以使用节点属性previousSibling/nextSibling来访问节点的同胞节点。
if (someNode.nextSibling === null){
alert("Last node in the parent’s childNodes list.");
} else if (someNode.previousSibling === null){
alert("First node in the parent’s childNodes list.");
}
父节点的firstChild和lastChild分别指向其子节点的第一个和最后一个节点。
另外hasChildNodes()也是一个好用的方法,当节点包含子节点的时候,该方法返回true。
所有节点都有的最后一个属性是ownerDocument,该属性指向表示整个文档的文档节点。
操作节点:
因为关系指针都是只读的,所以DOM提供了一些操作节点的方法。其中,最常用的就是appendChild()方法,用于向childNodes列表的末尾添加一个节点。更新完成后,appendChild()方法返回新增的节点。
var returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); //true
alert(someNode.lastChild == newNode); //true
如果传入appendChild函数的节点已经是文档的一部分了,那结果就是将传入的节点移动到指定的位置。
如果需要把指定节点放在childNodes列表的某个特定位置上,而不是最后一个,那么就需要用到insertBefore方法。这个方法接受两个参数:要插入的节点和作为参照的节点。如果参照节点是null,那么insertBefore方法就和appendChild方法的作用一样了。
//插入后成为最后一个节点
returnedNode = someNode.insertBefore(newNode, null);
alert(newNode == someNode.lastChild); //true
//插入后成为第一个节点
var returnedNode = someNode.insertBefore(newNode, someNode.firstChild); alert(returnedNode == newNode); //true
alert(newNode == someNode.firstChild); //true
//插入到最后一个节点前面
returnedNode = someNode.insertBefore(newNode, someNode.lastChild); alert(newNode == someNode.childNodes[someNode.childNodes.length-2]); //true
replaceChild函数:该函数接受两个参数,要插入的节点和要替换的节点。要替换的节点将由这个函数返回,同时要插入的节点将在DOM中取代被替换掉的节点。
//替换第一个子节点
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
//替换最后一个子节点
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
如果只想移除而不是替换节点的话,可以使用函数removeChild(),该函数接受一个参数:要移除的节点。
其它方法:
有两个方法是所有类型的节点都具有的:cloneNode(),normalize()。
cloneNode方法接受一个布尔值的参数,表示是否进行深复制。如果进行深复制,那么该节点的所有包括子节点在内的信息都会被复制。如果只是浅复制,那么将只复制节点自身。
例如:
<ul>
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>
假设我们已经将ul节点保存在了myList中,那么下面这段代码就可以让我们看出两种复制的区别。
var deepList = myList.cloneNode(true);
alert(deepList.childNodes.length); //3
var shallowList = myList.cloneNode(false);
alert(shallowList.childNodes.length); //0
normalize方法用来处理文档树中的文本节点。当在某个节点上调用这个方法时,就会在该节点的后代中查找:1)空文本节点,找到后删除;2)相邻的文本节点,找到后合并成一个。
2、Document类型:
JavaScript通过Document类型表示文档。在浏览器中,document是HTMLDocument的一个实例,表示整个HTML页面。而且document是window对象的一个属性,因此,可以将其作为全局属性来访问。
document节点具有下列属性:
nodeType值为9;
nodeName的值为#document;
nodeValue、parentNode、ownerDocument的值为null。
Document类型可以表示HTML页面,不过最常见的应用还是作为document对象。通过这个对象,不仅可以获得与页面有关的信息,而且还可以操作页面的外观极其底层结构。
1、文档的子节点
document的documentElement属性:该属性始终指向HTML页面中的html元素。另外,document的childNodes也可以用来访问页面的元素。
var html = document.documentElement;//获得对<html>的引用
alert(html === document.childNodes[0]);//true
alert(html === document.firstChild);//true
作为HTMLDocument的实例,document还有一个属性body,直接指向页面的body标签:
var body = document.body; //取得对<body>的引用
所有的浏览器都支持document.documentElemen和document.body属性。
2、文档信息
作为HTMLDocument文档的一个实例,document还有一些普通Document对象所没有的属性,这些属性提供了document对象所表现的一些网页信息。
例如,title:
//获得文档标题
var originalTitle = document.title;
//设置文档标题
document.title = "New page title";
接下来的三个属性都和网页请求有关:
//取得完整的URL
var url = document.URL;
//取得域名
var domain = document.domain;
//取得来源页面的URL
var referrer = document.referrer;
这三个属性中,只有domain是可以设置的。但是,出于安全性问题的考虑,只能设置成URL中包含的域,否则就会出错:
//假设页面来自于p2p.wrox.com域
document.domain = "wrox.com"; //成功
document.domain = "nczonline.net"; //出错
浏览器对domain属性还有一个限制,即如果域名一开始是松散的,就不能将其设置为紧绷的。
//假设页面来自于p2p.wrox.com域
document.domain = "wrox.com"; //松散的,成功
document.domain = "p2p.wrox.com"; //紧绷的,失败
3、查找元素
最常见的DOM应用,应该就是获得特定的某个或某组元素的引用,然后再执行一些操作了。取得元素的操作可以使用document对象的几个方法完成:例如,getElementById()、getElementsByTagName()、getElementsByTagName()。
例子:
var images = document.getElementsByTagName("img");
alert(images.length);
alert(images[0].src);
alert(images.item(0).src);
4、特殊集合
document.anchors:文档中所有带name属性的a标签
document.forms:文档中所有form标签
document.images:文档中所有img标签
document.links:文档中所有带href属性的a标签
5、DOM一致性检测
由于DOM包含了多个级别,也包含了多个部分,因此,检测浏览器实现了DOM的那些部分就十分重要了。
使用方法:
var hasXmlDom = document.implementation.hasFeature("XML", "1.0");
如果浏览器支持该版本,将返回true。
6、文档写入
将输出流写入网页的能力:
<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>
此外,还可以使用此方法动态的包含外部资源:
<html>
<head>
<title>document.write() Example 3</title>
</head>
<body>
<script type="text/javascript">
document.write("<script type=\"text/javascript\" src=\"file.js\">" +"<\/script>");
</script>
</body>
</html>
请注意双引号中的转意字符的写法。
Element类型
Element节点具有以下特点:
nodeType为1
nodeName/tagName为节点的标签名
nodeValue为null
parentNode可能是Document或Element
1、HTML元素
<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>
// 获取属性
var div = document.getElementById("myDiv");
alert(div.id);//"myDiv"
alert(div.className);//"bd"
alert(div.title);//"Body text"
alert(div.lang);//"en" 元素内容的语言代码
alert(div.dir);//"ltd" 语言的方向
// 修改属性
div.id = "someOtherId";
div.className = "ft";
div.title = "Some other text";
div.lang = "fr";
div.dir ="rtl";
2、取得特性
getAttribute()、setAttribute()、removeAttribute()三个方法:
var div = document.getElementById("myDiv");
alert(div.getAttribute("id"));//"myDiv"
alert(div.getAttribute("class"));//"bd"
alert(div.getAttribute("title"));//"Body text"
alert(div.getAttribute("lang"));//"en"
alert(div.getAttribute("dir"));//"ltr"
用这个方法可以获得标签自定义的属性:
<div id="myDiv" data-my_special_attribute="hello!"></div>
var value = div.getAttribute("my_special_attribute");
另外,有两个特殊的属性,它们通过getAttribute访问或者通过获取元素属性的值得到的结果不同:
第一个是style:当通过getAttribute方法访问时,返回的是字符串,而通过元素的属性的方法访问时,返回的则是一个对象。
第二个是onclick这样的事件处理程序:通过getAttribute访问时,返回的是JS代码的字符串,而通过元素属性访问,返回的则是这个函数。
3、设置特性
div.setAttribute("id", "someOtherId");
div.setAttribute("class", "ft");
div.setAttribute("title", "Some other text");
div.setAttribute("lang","fr");
div.setAttribute("dir", "rtl");
4、attributes属性
Element类型是使用attributes属性的唯一的一个DOM节点类型。attributes属性中包含一个NamedNodeMap,是一个动态的集合。NamedNodeMap对象拥有下列方法:
getNamedItem(name):返回nodeName属性等于name的节点。
removeNamedItem(name):从列表中移除nodeName属性等于name的节点。setNamedItem(node):向列表中添加节点,以节点的nodeName属性为索引。
item(pos):返回位于pos位置的节点。
<div id="first" class="mydiv"> 萝卜 </div>
// method 1
var element = document.getElementById("first");
var id = element.attributes.getNamedItem("class").nodeValue;
console.log(id);// media
// method 2
var element = document.getElementsByTagName("div");
var id = element[0].attributes.getNamedItem("class").nodeValue;
console.log(id);// mydiv
5、创建元素
var div = document.createElement("div");
// 为元素添加属性
div.id = "myNewDiv";
div.className = "box";
// 将元素加入DOM树
document.body.appendChild(div);
// IE下:
var div = document.createElement("<div id=\"myNewDiv\" class=\"box\"></div >");
6、元素的子节点
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
var element = document.getElementById("myList");
var num = element.childNodes.length;
console.log(num);// 7
在不是IE的浏览器下,上面这个ul包含了7个字节点:包括三个li节点和4个文本节点。正确的访问li的方法为:
for (var i=0, len=element.childNodes.length; i < len; i++){
if (element.childNodes[i].nodeType == 1){
//执行某些操作
}
}
// or
var ul = document.getElementById("myList");
var items = ul.getElementsByTagName("li");
Text类型
文本节点由Text类型表示,包含的是纯文本内容。text节点具有一下的特征:
nodeType为3
nodeName为#text
nodeValue为节点包含的文本
parentNode是一个element
没有子节点
可以使用下列方法操作文本:
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的文本
<!--没有文本节点-->
<div></div>
<!--有1个文本节点-->
<div> </div>
<!--有1个文本节点-->
<div>Hello World!</div>
var textNode = div.firstChild; // div.childNodes[0]
div.firstChild.nodeValue = "Some other message";
修改文本节点时需要注意,此时的字符串要经过html编码,也就是说,有些符号会被转译:
//输出结果是"Some <strong>other</strong> message" div.firstChild.nodeValue = "Some <strong>other</strong> message";
创建文本节点:
var textNode = document.createTextNode("<strong>Hello</strong> world!");
// 创建文本节点并加入DOM中。
var element = document.createElement("div");
element.className = "message";
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
document.body.appendChild(element);
规范化文本节点
该方法可以将多个文本节点合并:
var element = document.createElement("div");
element.className = "message";
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
var anotherTextNode = document.createTextNode("Yippee!");
element.appendChild(anotherTextNode);
document.body.appendChild(element);
alert(element.childNodes.length); //2
element.normalize();
alert(element.childNodes.length); //1
alert(element.firstChild.nodeValue); // "Hello world!Yippee!"
分割文本节点
该方法的作用和normalize刚好相反,是将一个文本节点分割为多个:
var element = document.createElement("div");
element.className = "message";
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
document.body.appendChild(element);
var newNode = element.firstChild.splitText(5);
alert(element.firstChild.nodeValue); //"Hello"
alert(newNode.nodeValue); //" world!"
alert(element.childNodes.length); //2
Comment类型
注释用Comment类型表示。特点:
nodeType值为8
nodeName为#comment
nodeValue为注释的内容
parentNode为Document或者Element
没有子节点
<div id="myDiv"><!--A comment --></div>
// 访问
var div = document.getElementById("myDiv");
var comment = div.firstChild;
alert(comment.data); //"A comment"
// 创建
var comment = document.createComment("A comment ");
DocumentType类型
仅有Firefox Safari Opera支持。该类型包含了与文档doctype有关的信息。
nodeType:10
nodeName:doctype的名称
nodeValue:null
parentNode:Document
没有子节点
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
alert(document.doctype.name); //"HTML"
Attr类型
元素的特性在DOM中用Attr类型来表示。
var attr = document.createAttribute("align");
attr.value = "left";
element.setAttributeNode(attr);
alert(element.attributes["align"].value); //"left"
alert(element.getAttributeNode("align").value); //"left"
alert(element.getAttribute("align")); //"left"
DOM操作技术
动态脚本
使用script元素可以向页面中插入JavaScript代码,一种方式是通过src链接外部文件,另一种方式就是在标签内直接写入代码。
动态脚本,指的是,页面加载时不存在,但是在将来的某一个时刻,通过修改DOM动态添加的脚本。动态创建脚本有两个方式:
1、动态加载外部的JS文件
<script type="text/javascript" src="client.js"></script>
创建这个节点的DOM代码如下所示:
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "client.js";
document.body.appendChild(script);
// 函数封装
function loadScript(url){
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
document.body.appendChild(script);
}
// 加载文件
loadScript("client.js");
2、行内方式
<script type="text/javascript">
function sayHi(){
alert("hi");
}
</script>
// DOM代码 不兼容IE
var script = document.createElement("script");
script.type = "text/javascript";
script.appendChild(document.createTextNode("function sayHi(){alert('hi');}"));
document.body.appendChild(script);
// 兼容IE
var script = document.createElement("script");
script.type = "text/javascript";
script.text = "function sayHi(){alert('hi');}";
document.body.appendChild(script);
// 函数封装
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');}");
动态样式
<link rel="stylesheet" type="text/css" href="styles.css">
// DOM代码
var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = "style.css";
var head = document.getElementsByTagName("head")[0];
head.appendChild(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("styles.css");
// 第二种方式
<style type="text/css">
body {
background-color: red;
}
</style>
// DOM方式
var style = document.createElement("style");
style.type = "text/css";
try{
style.appendChild(document.createTextNode("body{background-color:red}"));
} catch (ex){
style.styleSheet.cssText = "body{background-color:red}";
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(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}");