DOM
(文档对象模型)是针对
HTML
和
XML
文档的一个
API(应用程序编程接口)。DOM描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。
节点层次
以下面的
HTML
为例:
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<p>Hello World!</p>
</body>
</html>
文档节点是每个文档的根节点。在这个例子中,文档节点只有一个子节点,即
<html>元素,我们称之为
文档元素
。
在
HTML
页面中,文档元素始终都是
<html>
元素。在
XML
中,没有预定义的元素,因此任何元素都可能成为文档元素。
Node类型
JavaScript
中的所有节点类型都继承自
Node
类型,因此所有节点类型都共享着相同的基本属性和方法。
每个节点都有一个 nodeType 属性,用于表明节点的类型。
if (someNode.nodeType == Node.ELEMENT_NODE){ //在 IE 中无效
alert("Node is an element.");
}
如果二者相等,则意味着someNode 确实是一个元素。
由于
IE
没有公开
Node 类型的构造函数,为了确保跨浏览器兼容,最好还是将
nodeType
属性与数字值进行比较。
if (someNode.nodeType == 1){ //适用于所有浏览器
alert("Node is an element.");
}
nodeName 和 nodeValue 属性
要了解节点的具体信息,可以使用
nodeName
和
nodeValue
这两个属性。
if (someNode.nodeType == 1){
value = someNode.nodeName; //nodeName 的值是元素的标签名
}
在这个例子中,首先检查节点类型,看它是不是一个元素。如果是,则取得并保存
nodeName
的值。对于元素节点,nodeName
中保存的始终都是元素的标签名,而
nodeValue
的值则始终为
null
。
节点关系
每个节点都有一个
childNodes
属性,其中保存着一个
NodeList
对象。
NodeList
是一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。
下面的例子展示了如何访问保存在
NodeList
中的节点——可以通过方括号,也可以使用
item()方法。
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var count = someNode.childNodes.length;
我们在本书前面介绍过,对
arguments
对象使用
Array.prototype.slice()
方法可以将其转换为数组。
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;
}
// 备注:
这个 convertToArray()函数首先尝试了创建数组的最简单方式。如果导致了错误(说明是在
IE8 及更早版本中),则通过 try-catch 块来捕获错误,然后手动创建数组。
每个节点都有一个
parentNode
属性,该属性指向文档树中的父节点。
另外,
hasChildNodes()
也是一个非常有用的方法,这个方法在节点包含一或多个子节点的情况下返回 true
;
操作节点
appendChild
最常用的方法是appendChild(),用于向
childNodes
列表的末尾添加一个节点。
var returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); //true
alert(someNode.lastChild == newNode); //true
如果在调用
appendChild()
时传入了父节点的第一个子节点,那么该节点就会成为父节点的最后一个子节点,如下面的例子所示。
//someNode 有多个子节点
var returnedNode = someNode.appendChild(someNode.firstChild);
alert(returnedNode == someNode.firstChild); //false
alert(returnedNode == someNode.lastChild); //true
insertBefore
如果需要把节点放在
childNodes
列表中某个特定的位置上,而不是放在末尾,那么可以使用 insertBefore()方法。这个方法接受两个参数:要插入的节点和作为参照的节点。
//插入后成为最后一个子节点
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
replaceChild()方法接受的两个参数是:要插入的节点和要替换的节点。要替换的节点将由这个方法返回并从文档树中被移除,同时由要插入的节点占据其位置。
//替换第一个子节点
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
//替换最后一个子节点
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);
removeChild
如果只想移除而非替换节点,可以使用
removeChild()
方法。这个方法接受一个参数,即要移除的节点。被移除的节点将成为方法的返回值。
//移除第一个子节点
var formerFirstChild = someNode.removeChild(someNode.firstChild);
//移除最后一个子节点
var formerLastChild = someNode.removeChild(someNode.lastChild);
其他方法
cloneNode
cloneNode(),用于创建调用这个方法的节点的一个完全相同的副本。接受一个布尔值参数,表示是否执行深复制。true表示执行深复制,false表示浅复制。
<ul>
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>
// 深复制
var deepList = myList.cloneNode(true);
alert(deepList.childNodes.length); //3(IE < 9)或 7(其他浏览器)
// 浅复制
var shallowList = myList.cloneNode(false);
alert(shallowList.childNodes.length); //0
Document类型
文档的子节点
在浏览器中,
document
对象是
HTMLDocument
(继承自 Document
类型)的一个实例,表示整个
HTML
页面。而且,
document
对象是
window
对象的一个属性,因此可以将其作为全局对象来访问。
Document
节点具有下列特征:
- nodeType 的值为 9;
-
nodeName 的值为 "#document" ;
-
其子节点可能是一个 DocumentType (最多一个)、 Element (最多一个)、 ProcessingInstruction或 Comment 。
-
ownerDocument 的值为 null ;
-
parentNode 的值为 null ;
-
nodeValue 的值为 null ;
两个内置的访问其子节点的快捷方式:
- 第一个就是 documentElement属性,该属性始终指向 HTML 页面中的<html>元素。
-
另一个就是通过 childNodes 列表访问文档元素
documentElement
但通过
documentElement
属性则能更快捷、更直接地访问该元素
<html>
<body>
</body>
</html>
var html = document.documentElement; //取得对<html>的引用
alert(html === document.childNodes[0]); //true
alert(html === document.firstChild); //true
body
作为
HTMLDocument
的实例,
document
对象还有一个
body
属性,直接指向
<body>
元素。
var body = document.body; //取得对<body>的引用
DocumentType
Document 另一个可能的子节点是 DocumentType。
var doctype = document.doctype; //取得对<!DOCTYPE>的引用
文档信息
title
//取得文档标题
var originalTitle = document.title;
//设置文档标题
document.title = "New page title";
URL、domain和 referrer
//取得完整的 URL
var url = document.URL;
//取得域名
var domain = document.domain;
//取得来源页面的 URL
var referrer = document.referrer;
查找元素
getElementById
第一个方法,
getElementById()
,接收一个参数:要取得的元素的
ID
。
<div id="myDiv">Some text</div>
// 可以使用下面的代码取得这个元素:
var div = document.getElementById("myDiv"); //取得<div>元素的引用
getElementsByTagName
这个方法接受一个参数,即要取得元素的标签名,而返回的是包含零或多个元素的 NodeList
。
var images = document.getElementsByTagName("img");
alert(images.length); //输出图像的数量
alert(images[0].src); //输出第一个图像元素的 src 特性
alert(images.item(0).src); //输出第一个图像元素的 src 特性
namedItem
使用这个方法可以通过元素的
name特性取得集合中的项。
<img src="myimage.gif" name="myImage">
var myImage = images.namedItem("myImage");
要想取得文档中的所有元素,可以向
getElementsByTagName()
中传入
"*"
。
var allElements = document.getElementsByTagName("*");
getElementsByName
这个方法会返回带有给定
name
特性的所有元素。
<fieldset>
<legend>Which color do you prefer?</legend>
<ul>
<li><input type="radio" value="red" name="color" id="colorRed">
<label for="colorRed">Red</label></li>
<li><input type="radio" value="green" name="color" id="colorGreen">
<label for="colorGreen">Green</label></li>
<li><input type="radio" value="blue" name="color" id="colorBlue">
<label for="colorBlue">Blue</label></li>
</ul>
</fieldset>
var radios = document.getElementsByName("color");
特殊集合
- document.anchors,包含文档中所有带 name 特性的<a>元素;
-
document.forms ,包含文档中所有的 <form> 元素 document.getElementsByTagName("form")得到的结果相同;
-
document.links ,包含文档中所有带 href 特性的 <a> 元素。
-
document.images ,包含文档中所有的 <img> 元素,与 document.getElementsByTagName("img") 得到的结果相同;
Element类型
Element
节点具有以下特征:
- nodeType 的值为 1;
- nodeName 的值为元素的标签名;
- nodeValue 的值为 null;
- parentNode 可能是 Document 或 Element;
- 其子节点可能是 Element、Text、Comment、ProcessingInstruction、CDATASection 或 EntityReference。
要访问元素的标签名,可以使用
nodeName
属性,也可以使用
tagName
属性;
<div id="myDiv"></div>
//可以像下面这样取得这个元素及其标签名:
var div = document.getElementById("myDiv");
alert(div.tagName); //"DIV"
alert(div.tagName == div.nodeName); //true
div.tagName
实际上输出的是"DIV"而非
"div"
。在
HTML
中,标签名始终都以全部大写表示;
if (element.tagName.toLowerCase() == "div"){ //这样最好(适用于任何文档)
//在此执行某些操作
}
操作特性的DOM 方法主要有三个,分别是 getAttribute()、setAttribute()和 removeAttribute()。
getAttribute
取得特性
<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>
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"
setAttribute
设置特性这个方法接受两个参数:要设置的特性名和值。
div.setAttribute("id", "someOtherId");
div.setAttribute("class", "ft");
div.setAttribute("title", "Some other text");
div.setAttribute("lang","fr");
div.setAttribute("dir", "rtl");
removeAttribute
这个方法用于彻底删除元素的特性。
div.removeAttribute("class");
attributes 属性
attributes
的方法不够方便,因此开发人员更多的会使用,getAttribute()、removeAttribute()
和
setAttribute()
方法。
Element
类型是使用
attributes
属性的唯一一个
DOM
节点类型。
- getNamedItem(name):返回 nodeName 属性等于 name 的节点;
- removeNamedItem(name):从列表中移除 nodeName 属性等于 name 的节点;
- setNamedItem(node):向列表中添加节点,以节点的 nodeName 属性为索引;
- item(pos):返回位于数字 pos 位置处的节点。
getNamedItem
var id = element.attributes.getNamedItem("id").nodeValue;
// 以下是使用方括号语法通过特性名称访问节点的简写方式。
var id = element.attributes["id"].nodeValue;
nodeValue
设置为新值
element.attributes["id"].nodeValue = "someOtherId";
removeNamedItem
removeNamedItem()
返回表示被删除特性的 Attr
节点。
var oldAttr = element.attributes.removeNamedItem("id");
setNamedItem
setNamedItem()是一个很不常用的方法,通过这个方法可以为元素添加一个新特性。
element.attributes.setNamedItem(newAttr);
创建元素
createElement
var div = document.createElement("div");
// 添加子节点
div.id = "myNewDiv";
div.className = "box";
要把新元素添加到文档树,可以使用
appendChild()
、
insertBefore()或
replaceChild()
方法。
appendChild
// 会把新创建的元素添加到文档的<body>元素中
document.body.appendChild(div);
元素的子节点
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
var ul = document.getElementById("myList");
var items = ul.getElementsByTagName("li");
for (var i=0, len=items.length; i < len; i++){
if (items[i].nodeType == 1){
//执行某些操作
}
}
DOM 操作技术
动态脚本
function loadScript(url){
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
document.body.appendChild(script);
}
// 过调用这个函数来加载外部的 JavaScript 文件
loadScript("client.js");
问题只有一个:怎么知道脚本加载完成呢?遗憾的是,并没有什么标准方式来探知这一点。
另一种指定
JavaScript
代码的方式是行内方式,
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>
元素添加到
<head>而不是<body>
元素,才能保证在所有浏览器中的行为一致。
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()函数
loadStyles("styles.css");
另一种定义样式的方式是使用
<style>
元素来包含嵌入式
CSS
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}");
操作表格
详见300
//创建 table
var table = document.createElement("table");
table.border = 1;
table.width = "100%";
//创建 tbody
var tbody = document.createElement("tbody");
table.appendChild(tbody);
//创建第一行
tbody.insertRow(0);
tbody.rows[0].insertCell(0);
tbody.rows[0].cells[0].appendChild(document.createTextNode("Cell 1,1"));
tbody.rows[0].insertCell(1);
tbody.rows[0].cells[1].appendChild(document.createTextNode("Cell 2,1"));
//创建第二行
tbody.insertRow(1);
tbody.rows[1].insertCell(0);
tbody.rows[1].cells[0].appendChild(document.createTextNode("Cell 1,2"));
tbody.rows[1].insertCell(1);
tbody.rows[1].cells[1].appendChild(document.createTextNode("Cell 2,2"));
//将表格添加到文档主体中
document.body.appendChild(table);