DOM 就是文档的数据结构,它提供了操作文档的编程接口 API。
10.1 节点层次
文档是由节点组成的树形结构,根节点是文档节点 Documet
,其下是 html
元素节点。
10.1.1 Node 类型
节点的类型
总共有 12 种类型的节点,使用 Node
类可以判断一个节点的类型。注意:在 IE 中只能使用数值,不能使用常量。
常量 | 值 | 说明 |
---|---|---|
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.");
}
节点的信息
每个节点都有 nodeType
、nodeName
和 nodeValue
三个属性,可以了解节点的信息。
if (someNode.nodeType == 1){
value = someNode.nodeName; //将会返回标签名
}
不同节点各属性的含义
不同类型的节点,nodeName
和 nodeValue
的意义不一样,因此,一般情况下要先判断节点类型。
元素节点各属性的含义
名称 | 说明 |
---|---|
nodeName |
元素的标签名称,等同之前的 tagName
|
nodeType |
1 |
nodeValue |
元素节点没有值,所以返回 null
|
属性节点各属性的含义
名称 | 说明 |
---|---|
nodeName |
属性名称 |
nodeType |
2 |
nodeValue |
属性值 |
文本节点各属性的含义
名称 | 说明 |
---|---|
nodeName |
#text |
nodeType |
3 |
nodeValue |
文本内容(不包含 html) |
节点的关系
节点的层次结构可以划分为:父子节点、 兄弟节点这两种。 利用节点的层次结构可以获取节点树上的任意其他节点
属性 | 说明 |
---|---|
childNodes |
获取当前元素节点的所有子节点,返回一个 NodeList 对象 |
parentNode |
获取当前节点的父节点 |
previousSibling |
获取当前节点的前一个同级节点,第一个节点会返回 null
|
nextSibling |
获取当前节点的后一个同级节点,最后一个节点会返回 null
|
firstChild |
获取当前元素节点的第一个子节点 |
lastChild |
获取当前元素节点的最后一个子节点 |
ownerDocument |
获取该节点的文档根节点,相当于文档对象 document |
hasChildNodes |
判断是否有子节点 |
子节点列表
获取字节的的属性 childNodes
是个列表对象,可以像数组一样访问它
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var count = someNode.childNodes.length;
也可以用数组的 slice
方法将 childNodes
对象转换为数组
//在 IE8 之前不工作
var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0);
节点的增删改查
方法 | 说明 |
---|---|
appendChild |
将新节点追加到子节点列表的末尾,返回添加的节点 |
insertBefore |
将新节点插入在某个节点的前面,返回被插入的节点 |
repalceChild |
将新节点替换旧节点,返回被替换的节点 |
removeChild |
移除节点,返回被移除的节点 |
cloneNode |
克隆一个节点,可以深克隆,也可以浅克隆 |
添加节点
var returnedNode = someNode.appendChild(newNode);
alert(returnedNode == newNode); //true
alert(someNode.lastChild == newNode); //true
如果被添加的节点是已经文档中的节点,那么节点将被移动位置
//将第一个节点移动到最后
var returnedNode = someNode.appendChild(someNode.firstChild);
alert(returnedNode == someNode.firstChild); //false
alert(returnedNode == someNode.lastChild); //true
插入节点
//位置参数为 Null,则插入到最后
returnedNode = someNode.insertBefore(newNode, null);
alert(newNode == someNode.lastChild); //true
//插入到第一个
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
深克隆和浅克隆
var deepList = myList.cloneNode(true);
alert(deepList.childNodes.length); //3 (IE < 9) or 7 (others)
var shallowList = myList.cloneNode(false);
alert(shallowList.childNodes.length); //0
10.1.2 Document 类型
文档对象代表整个页面文档,是 DOM 的根节点,它是只读的,因此不能添加、删除、替换子节点。其下一般只有惟一一个 html
子节点,是系统自动创建的
属性 | 值 |
---|---|
nodeType |
9 |
nodeName |
"#document" |
nodeValue |
null |
parentNode |
null |
ownerDocument |
null |
子节点 |
可以有一个 DocumentType 一个 Element 或者一个注释等等 |
文档对象的属性
除了具有普通节点的属性和方法之外,document 对象还有一些快捷属性
属性 | 说明 |
---|---|
document.documentElement | 返回文档包含的子节点,指向 html 元素 |
document.body | 指向 body 元素 |
document.doctype | 指向 doctype 元素,各个浏览器对这个属性不一致,因此不常用 |
document.title | 返回文档标题 |
document.URL | 返回文档的链接 |
document.referrer | 返回链接到当前页面的那个页面的链接,可能为空 |
document.domain | 返回域名,不能设置为 URL 中不包含的域 |
document.documentElement 的例程
var html = document.documentElement; //返回 <html> 元素对象
alert(html === document.childNodes[0]); //true
alert(html === document.firstChild); //true
文档对象的域
修改域的时候,只能修改子域
//page from p2p.wrox.com
document.domain = "wrox.com"; //成功
document.domain = "nczonline.net"; //不在同一个域中,出错!
设置为同样的主域名,不同页面之间的对象可以进行通信
//page from p2p.wrox.com
document.domain = "wrox.com"; //可以通信
document.domain = "p2p.wrox.com"; //这样不能通信
查找元素
利用文档对象可以查找元素对象。
注意,必须等文档加载完毕,才能查找和操作 DOM 操作对象,可以将相关代码放在 onload 事件中
方法 | 说明 |
---|---|
document.getElementsById | 返回一个元素 |
document.getElementsByTagName | 返回同一种标签的集合,可以用通配符 |
document.getElementsByName | 返回有相同名字的元素集合,可以用通配符 |
元素集合
getElementsByTagName 和 getElementsByName 这两个查找方法会返回元素的集合
var images = document.getElementsByTagName("img");
alert(images.length); //output the number of images
alert(images[0].src); //output the src attribute of the first image
alert(images.item(0).src); //output the src attribute of the first image
var myImage = images.namedItem("myImage");
var myImage = images["myImage"];
特殊集合的快捷访问
文档对象有几个属性,可以直接获取某些元素集合
属性 | 说明 |
---|---|
document.anchors | 锚点集合 |
document.applets | 小程序集合 |
document.forms | 表单集合 |
document.images | 图像集合 |
document.links | 链接集合 |
DOM 一致性检测
DOM 本身分很多级别,不同浏览器实现的程度也有所不同,可以用 document.implementation.hasFeature 方法检测
var hasXmlDom = document.implementation.hasFeature("XML", "1.0");
下面的表格时可以进行检测的功能和版本号
功能 | 版本号 | 说明 |
---|---|---|
Core | 1.0, 2.0, 3.0 | |
XML | 1.0, 2.0, 3.0 | |
HTML | 1.0, 2.0 | |
Views | 2.0 | |
StyleSheets | 2.0 | |
CSS | 2.0 | |
CSS2 | 2.0 | |
Events | 2.0, 3.0 | |
UIEvents | 2.0, 3.0 | |
MouseEvents | 2.0, 3.0 | |
MutationEvents | 2.0, 3.0 | |
HTMLEvents | 2.0 | |
Range | 2.0 | |
Traversal | 2.0 | |
LS | 3.0 | |
LS-Async | 3.0 | |
Validation | 3.0 |
文档写入
document.write
方法可以向文档写入内容,有两个需要注意的地方
- 包含
"</script>"
这个特殊字符串的时候要转义一下 - 当文档加载完毕后调用
document.write
方法,将重写整个文档
document.write("<script type=\"text/javascript\" src=\"file.js\">" + "<\/script>");
10.1.3 Element 类型
元素对象是网页中最基本的对象
属性 | 值 |
---|---|
nodeType |
1 |
nodeName |
元素的标签名 |
nodeValue |
null |
parentNode |
null |
ownerDocument |
Document 或者 Element
|
子节点 |
获取元素对象的标签名
除了用节点的 nodeName 之外,也可以直接用 tagName 属性获取标签名
if (element.tagName == "div"){ //AVOID! Error prone!
//do something here
}
if (element.tagName.toLowerCase() == "div"){ //Preferred - works in all documents
//do something here
}
元素的属性
元素对象的所有属性都可以读取和设置
属性 | 说明 |
---|---|
tagName | 获取元素节点的标签名,一般返回大写字符串 |
innerHTML | 获取元素节点里的内容,非 W3C DOM 规范,但是个浏览器都支持 |
id | 元素节点的 id 名称 |
title | 元素节点的 title 属性值 |
style | CSS 内联样式属性值 |
className | CSS 元素的类,因为 class 是关键字,所以使用 className
|
bbb | 不支持自定义属性, |
onclick | 返回事件函数的代码 |
元素的特性
通过特性除了可以访问元素的属性之外,还可以:
- 自定义特性,前缀为 data- ,自定义特性名都被转换为小写
- 通过属性访问 style 返回的是对象,通过特性访问 style 返回字符串
- 通过属性访问事件,返回函数对象,通过特性访问事件,返回字符串
特性的相关方法
- getAttribute() 方法
- setAttribute() 方法
- removeAttribute() 方法
特性对象数组 attributes
attributes 是包含特性对象的键值对数组,可以通过名字或者索引号获取其中的对象
attrName = element.attributes[i].nodeName;
element.attributes["id"].nodeValue = "someOtherId";
可以通过特性对象数据增删改查元素的特性,但是相关方法并不方便,不如直接使用元素对象的特性相关方法。
所以,一般情况下不使用特性,只在枚举特性和自定义特性的情况下使用
动态创建元素对象
可以用 document.createElement() 方法动态创建元素对象:
- 通常情况下传入标签名
- 对于老式浏览器,传入 HTML 代码
var div = document.createElement("div");
var div2 = document.createElement("<div id=\"myNewDiv\" class=\"box\"></div>");
div.id = "myNewDiv";
div.className = "box";
document.body.appendChild(div);
不同浏览器对空白的不同解释
大部分浏览器都将子元素之间的换行符也当做一个文本元素子节点;IE 浏览器只将子元素作为子节点,忽略换行符。
因此,统计子节点时可以对节点类型进行一个判断
for (var i=0, len=element.childNodes.length; i < len; i++){
if (element.childNodes[i].nodeType == 1){
//do processing
}
}
10.1.4 Text 类型
文本节点一般是元素对象中的文字内容,本身也是一个节点
属性 | 值 |
---|---|
nodeType |
3 |
nodeName |
#text |
nodeValue |
文本内容 |
parentNode |
包含该文本的元素节点 |
子节点 |
无 |
下面是文本节点的增删改查方法及相关属性
属性或方法 | 值 |
---|---|
appendData(text) |
添加 |
deleteData(offset, count) |
删除 |
insertData(offset, text) |
插入 |
replaceData(offset, count, text) |
替换 |
splitText(offset) |
切分 |
substringData(offset, count) |
子串 |
length |
长度 |
元素最多只能包含一个文本节点,而且必须有内容存在
下面代码中空格也是一个文本节点
<!-- 空格也是一个文本节点 -->
<div> </div>
给文本节点设置新的内容,特殊字符会自动被转义
//输出结果是: "Some <strong>other</strong> message"
div.firstChild.nodeValue = "Some <strong>other</strong> message";
创建文本节点
document.createTextNode() 方法可以创建文本节点
var element = document.createElement("div");
element.className = "message";
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
document.body.appendChild(element);
规范化文本节点
在一个元素内可以添加多个文本节点,规范化方法 element.normalize() 可以将这些文本节点合并起来
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!"
切分文本节点
切分文本节点可以将文本从指定位置处切分,原文本节点保留切分点之前内容,返回一个新文本节点包含切分点之后的内容
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
10.1.5 Commet 类型
注释类型和文本类型有同样的父类,这个类型在实际项目中用处不大,可以忽略。
10.1.6 CDATASection 类型
该类型一般只出现在 XML 文档中,对 HTML 文档用处不大,可以忽略。
10.1.7 DocumentType 类型
该类型对象不能动态创建,只能通过 document.doctype 获取
10.1.8 DocumentFragment 类型
- 有时候需要一次在文档树中添加多个节点,会导致页面频繁刷新。
- 这时候可以创建一个 DocumentFragment 对象,这个对象和文档中的 Node 一样,但是不会刷新页面,也不会显示。
- 将需要添加的节点添加到文档碎片对象之下,添加完毕后,再将文档碎片对象添加到文档中的目标节点下。这样,文档碎片对象的子节点会自动成为目标节点的子节点
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);
10.1.9 Attr 类型
Attr 类型就是特性对象的类型,attributes 属性中的对象都是特性对象。特性节点不出在 DOM 树中,但是可以承载信息。
特性对象有三个常用属性:name、value 和 specified ,其中 specified 用来确定特性是默认值还是用户设置的值
document.createAttribute() 方法可以创建特性节点
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"
10.2 DOM 操作技术
10.2.1 动态脚本
动态加载外部脚本
function loadScript(url){
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
document.body.appendChild(script);
}
loadScript("client.js");
动态生成内联脚本
创建脚本节点对象以后,可以为脚本节点对象在创建一个文本子节点,其中包含代码。但是 IE不支持这种方法,可以直接赋值给 script.text
function loadScriptString(code){
var script = document.createElement("script");
script.type = "text/javascript";
try {
//适用于现代浏览器
script.appendChild(document.createTextNode(code));
} catch (ex){
//适用于 IE
script.text = code;
}
document.body.appendChild(script);
}
loadScriptString("function sayHi(){alert(‘hi’);}");
10.2.2 动态样式
动态加载外部样式表
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");
动态加载内联样式表
function loadStyleString(css){
var style = document.createElement("style");
style.type = "text/css";
try{
//适用于现代浏览器
style.appendChild(document.createTextNode(css));
} catch (ex){
//适用于 IE
style.styleSheet.cssText = css;
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);
}
loadStyleString("body{background-color:red}");
10.2.3 操作表格
如果用普通的节点方法动态操作表格非常麻烦,下面是一个表格的 HTML 代码
<table border="1" width="100%">
<tbody>
<tr>
<td>Cell 1,1</td>
<td>Cell 2,1</td>
</tr>
<tr>
<td>Cell 1,2</td>
<td>Cell 2,2</td>
</tr>
</tbody>
</table>
用普通节点方法像下面这样
//创建表格对象
var table = document.createElement("table");
table.border = 1;
table.width = "100%";
//创建 tbody
var tbody = document.createElement("tbody");
table.appendChild(tbody);
//创建第一行
var row1 = document.createElement("tr");
tbody.appendChild(row1);
var cell1_1 = document.createElement("td");
cell1_1.appendChild(document.createTextNode("Cell 1,1"));
row1.appendChild(cell1_1);
var cell2_1 = document.createElement("td");
cell2_1.appendChild(document.createTextNode("Cell 2,1"));
row1.appendChild(cell2_1);
//创建第二行
var row2 = document.createElement("tr");
tbody.appendChild(row2);
var cell1_2 = document.createElement("td");
cell1_2.appendChild(document.createTextNode("Cell 1,2"));
row2.appendChild(cell1_2);
var cell2_2= document.createElement("td");
cell2_2.appendChild(document.createTextNode("Cell 2,2"));
row2.appendChild(cell2_2);
//加入到文档中
document.body.appendChild(table);
其实表格对象有一些自己特定的属性和方法
属性或方法 | 说明 |
---|---|
caption | |
tBodies | |
tFoot | |
tHead | |
rows | |
createTHead() | |
createTFoot() | |
createCaption() | |
deleteTHead() | |
deleteTFoot() | |
deleteCaption() | |
rows | |
deleteRow(pos) | |
insertRow(pos) | |
cells | |
deleteCell(pos) | |
insertCell(pos) |
利用这些属性和方法创建表格相对简单一点
//创建表格
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);
10.2.4 使用 NodeList
查询获取的 NodeList 数组是动态的,只要文档发生了变化, NodeList 也发生对应的变化。下面的代码会死循环
var divs = document.getElementsByTagName("div"),
i,div;
for (i=0; i < divs.length; i++){
//创建新的 div 元素导致 NodeList 的长度动态发生变化,造成死循环
div = document.createElement("div");
document.body.appendChild(div);
}
为了达到上述代码想实现的目标,同时避免死循环,可以在循环之前获取 NodeList 的长度
var divs = document.getElementsByTagName("div"),
i,len,div;
//用临时变量存储 NodeList 的长度,这样去不受动态变化的影响
len=divs.length;
for (i=0; i < len; i++){
div = document.createElement("div");
document.body.appendChild(div);
}