DOM 描述了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。
10.1、节点层次
DOM 可以将任何 HTML 或 XML 文档描绘成一个由多层节点构成的结构。
所有页面标记则表现为一个以特定节点为根节点的树形结构。
HTML 中的每一段标记都可以通过树中的一个节点来表示:HTML 元素通过元素节点表示,特性通过特性节点表示,文档类型通过文档类型节点表示,注释则通过注释节点表示。
10.1.1、Node 类型
JS 中的所有节点类型都继承自 Node 类型,因此所有节点类型都共享着相同的基本属性和方法。
每个节点都有一个 nodeType 属性,用于表明节点的类型。用来表示节点类型的 Node 类型下的12个数值常量:
- Node.ELEMENT_NODE(1);
- Node.ATTRIBUTE_NODE(2);
- Node.TEXT_NODE(3);
- Node.CDATA_SECTION_NODE(4);
- Node.ENTITY_PEFERENCE_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);
console.log(document.nodeType); // 9
1. nodeName 和 nodeValue 属性
注意:对于元素节点,nodeName 中保存的始终都是元素的标签名,而 nodeValue 的值则始终为 null。
<h1>Hello world!</h1>
<script>
var h1 = document.getElementsByTagName('h1');
if (h1[0].nodeType == 1) {
console.log(h1[0].nodeName); // H1
}
</script>
2.节点关系
每个节点都有 childNodes 属性,其中保存着一个 NodeList 对象。
注意:NodeList 是一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。
<div id="container">
<h1>Wiki</h1>
<p>A wiki is run using wiki software, otherwise known as a wiki engine. A wiki engine is a type of content management system.</p>
</div>
<script>
var div = document.getElementById('container');
var firstChild = div.childNodes[0];
var secondChild = div.childNodes.item(0);
console.log(firstChild === secondChild); // true
var count = div.childNodes.length;
console.log(div.childNodes); // NodeList(5) [text, h1, text, p, text]
console.log(count); // 5
</script>
每一个节点都有 parentNode 属性,该属性指向文档树中的父节点。
- previousSibling:和该节点在同一列表中的前一个节点,该列表中第一个节点的 previousSibling 为 null;
- nextSibling:和该节点在同一列表中的后一个节点,该列表中最后一个节点的 nextSibling 为 null。
当节点包含一个或多个子节点时,hasChildNodes() 方法返回 true。
<div id="container">
<h1>Wiki</h1>
<p>A wiki is run using wiki software, otherwise known as a wiki engine. A wiki engine is a type of content management system.</p>
</div>
<script>
var div = document.getElementById('container');
console.log(div.hasChildNodes()); // true
</script>
3.操作节点
(1)appendChild() 方法,用于向 childNodes 列表的末尾添加一个节点。
注意:如果传入 appendChild() 中的节点已经是文档的一部分,那结果就是将该节点从原来的位置转移到新位置。
(2)insertBefore() 把节点放在 childNodes 列表中某个特定的位置,该方法接受两个参数:要插入的节点和作为参照的节点。
(3)replaceChild() 替换节点,该方法接受两个参数:要插入的节点和要替换的节点;该方法返回要替换的节点。
(4)removeChild() 移除节点,该方法只接受一个参数:要移除的节点;该方法返回移除的节点。
<div id="container">
<h1>Wiki</h1>
<p>A wiki is run using wiki software</p>
<p>otherwise known as a wiki engine</p>
</div>
<script>
var div = document.getElementById('container');
var h1 = document.getElementsByTagName('h1')[0];
var p =document.getElementsByTagName('p');
//div.appendChild(h1); // h1会在第三行显示
div.insertBefore(h1, p[1]); // h1会在第二行显示
h2.innerHTML = 'Baidu';
div.replaceChild(h2, h1); // 替换节点,该方法返回被替换的节点
div.removeChild(h2); // h2元素被移除
</script>
4.其他方法
所有节点类型都有的两个方法:(1)cloneNode();(2)normalize()。
(1)cloneNode() 用于复制节点,该方法只接受一个布尔值参数。如果为 true,则表示执行深复制(复制节点及子节点);如果为 false,则表示执行浅复制(只复制节点本身)。
<body>
<div id="container">
<ul>
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>
</div>
<script>
var body = document.body;
var div = document.getElementById('container');
var deepList = div.cloneNode(true);
console.log(deepList);
body.appendChild(deepList);
var shallowList = div.cloneNode(false);
console.log(shallowList);
</script>
(2)normalize() 处理文档树中的文本节点。当某个节点调用该方法时,就会在该节点的后代节点中查找 出现文本节点不包含文本 或 接连出现两个文本节点 的情况,则进行删除。
10.1.2、Document 类型
document 对象是 HTMLDocument(继承自 Document 类型)的一个实例,表示整个HTML页面。而且,document 对象是 window 对象的一个属性,因此可以将其作为全局对象来访问。Document 节点具有下列特征:
- nodeType 的值为 9
- nodeName 的值为 "#document"
- nodeValue 的值为 null
- parentNode 的值为 null
- ownerDocument 的值为 null
- 其子节点可能是一个 DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction 或 Comment。
1.文档的子节点
documentElement 属性:无论 HTML 页面中是否存在 <!DOCTYPE> ,该属性始终指向 <html> 元素。
<html>
<body>
</body>
<script>
var rootHtml = document.documentElement;
console.log(rootHtml === document.childNodes[0]); // true
console.log(rootHtml === document.firstChild); // true
</script>
</html>
body 属性直接指向 <body> 元素。
注意:所有浏览器都支持 document.documentElement 和 document.body 属性。
可以通过 docoument.doctype 属性来获取 <!DOCTYPE> 标签
2.文档信息
document 对象还有一些标准的 Document 对象所没有的属性:
(1)title:<title>
(2)与网页请求有关:URL、domain、referrer
URL 属性包含页面完整的 URL;
domain 属性中只包含页面的域名;
referrer 属性中则保存着链接到当前页面的那个页面的 URL。
3.查找元素
getElementById():只返回文档中第一次出现的元素
getElementsByTagName():返回文档中所有同名元素,并返回一个 HTMLCollection 对象。 HTMLCollection 对象可以通过调用 namedItem() 方法来根据 name 特性取得集合中的项。
getElementsByName():返回带有给定 name 特性的所有元素。
4.特殊集合
除了属性和方法,document 对象还有一些特殊的集合:
- document.anchors,包含文档中所有带 name 特性的 <a> 元素
- document.forms,包含文档中所有的 <form> 元素,与 document.getElementsByTagName("form")结果相同
- document.images,包含文档中所有的 <img> 元素,与 document.getElementsByTagName("img")结果相同
- document.links,包含文档中所有带 href 特性的 <a> 元素。
6.文档写入
将输出流写入到网页中:(1)write()、(2)writeln()、(3)open、(4)close()。
(1)write():原样写入
(2)writeln():在字符串的末尾添加一个换行符(\n)
注意:上述两个方法在页面被加载的过程中,可以使用这两个方法向页面中动态地加入内容。
注意:如果在文档加载结束后在调用 document.write() ,那么输出的内容将会重写整个页面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Js高级程序设计 第10章测试</title>
</head>
<body>
<p>The current date and time is:</p>
<script>
document.write("<strong>" + (new Date()).toString() + "</strong>"); // 输出内容添加到<p>元素的下一行
// 当文档加载结束后调用 document.write(),则会重写这个页面
window.onload = function() {
document.write("<strong>" + (new Date()).toString() + "</strong>"); // 整个页面只显示该内容
};
</script>
</body>
</html>
open() 和 close() 方法分别用于打开和关闭网页的输出流。如果是在页面加载期间使用 write() 或 writeln() 方法,则不需要用到这两个方法。
10.1.3、Element 类型
Element 类型用于表现 XML 或 HTML 元素,提供了对元素标签名、子节点及特性的访问。Element 节点具有如下特征:
- nodeType 的值为 1;
- nodeName 的值为元素的标签名;
- nodeValue 的值为 null;
- parentNode 可能是 Document 或 Element;
- 其子节点可能是 Element、Text、Comment、ProcessingInstruction、CDATASection 或 EntityReference。
访问元素的标签名可以使用属性:nodeName 或 tagName
<div id="myDiv"></div>
<script>
var myDiv = document.getElementById('myDiv');
console.log(myDiv.nodeName); // DIV
console.log(myDiv.nodeName === myDiv.tagName); // true
</script>
1.HTML 元素
所有 HTML 元素都由 HTMLElement 类型表示,不是直接通过这个类型,也是通过它的子类型来表示。HTMLElement 类型直接继承自 Element 类型并添加了一些属性。添加的属性分别对应于每个 HTML 元素中都存在的下列标准特性:
- id,元素在文档中的唯一标识符
- title,有关元素的附加说明信息,鼠标移动到该元素显示的提示信息
- lang,元素内容的语言代码,很少使用
- dir,语言的方向,值为 "ltr"(left-to-right,从左至右)或 "rtl"(right-to-left,从右至左),很少使用。修改后即可体现。
- className,与元素的 class 特性对应
<div id="myDiv" class="bd" title="body text" lang="en" dir="ltr">测试</div>
<script>
var myDiv = document.getElementById('myDiv');
console.log(myDiv.nodeName); // DIV
console.log(myDiv.id); // 'myDiv'
console.log(myDiv.className); // 'bd'
console.log(myDiv.title); // 'body text'
console.log(myDiv.lang); // 'en'
console.log(myDiv.dir); // 'ltr'
</script>
2.提取特性
操作元素特性的三个方法:(1)getAttribute();(2)setAttribute();(3)removeAttribute()。
(1)getAttribute() :获取元素特性的值,如果该特性不存在,则返回 null。元素公认的特性可以通过属性来访问,自定义的特性则只能通过该方法来访问。
注意:根据 H5 规范,自定义特性应该加上 data- 前缀以便验证。
<div id="myDiv" class="bd" title="body text" lang="en" dir="ltr" my_special_attribute="hello!">测试</div>
<script>
var myDiv = document.getElementById('myDiv');
console.log(myDiv.id); // 'myDiv'
console.log(myDiv.className); // 'bd'
console.log(myDiv.getAttribute('my_special_attribute')); // 'hello!'
</script>
有两类特殊的特性,它们虽然有对于的属性名,单属性的值与通过 getAttribute() 返回的值并不相同。
第一类特性就是 style,用于通过 CSS 为元素指定样式。在通过 getAttribute() 访问时,返回的是 style 特性值中包含的是 CSS 文本;而通过属性来访问它则返回一个对象。
第二类特性就是 onclick 这样的事件处理程序。通过 getAttribute() 访问时,返回的是 onclick 特性值中包含相应代码的字符串;而通过属性来访问它则会返回一个 JS 函数(如果未在元素中指定相应特性,则返回 null)。
<div id="myDiv2" style="width:400px;height:400px;background-color:red;">测试2</div>
<button id="button" onclick="alert('hello!');">按钮</button>
<script>
// 特殊的特性:style
var myDiv2 = document.getElementById('myDiv2');
console.log(myDiv2.getAttribute('style')); // 'width:400px;height:400px;background-color:red;'
console.log(myDiv2.style); // 返回对象
// 特殊特性:onclick(时间处理程序)
var button = document.getElementById('button');
console.log(button.getAttribute('onclick')); // 返回代码
console.log(button.onclick); // 返回函数
</script>
3.设置特性
setAttribute() 设置特性,该方法接受两个参数:要设置的特性 和 值。
注意:通过该方法设置的特性名会被同一转换为小写。
removeAttribute() 彻底删除特性。
4.attributes 属性
Element 类型是使用 attributes 属性的唯一一个 DOM 节点类型。attributes 属性中包含一个 NamedNodeMap,与 NodeList 类似,也是一个 “动态” 的集合。NamedNodeMap 对象拥有下列方法:
- getNamedItem(name):返回 nodeName 属性等于 name 的节点;
- removeNamedItem(name):从列表中移除 nodeName 属性等于 name 的节点;
- setNamedItem(name):向列表中添加节点,以节点的 nodeName 属性为索引;
- item(pos):返回位于数字 pos 位置处的节点。
attributes 属性中包含一系列节点,每个节点的 nodeName 就是特性的名称,而节点的 nodeValue 就是特性的值。
注意:遍历元素的特性,则使用 attributes 属性非常方便。
<div id="myDiv" class="bd" title="body text" lang="en" dir="ltr" my_special_attribute="hello!">测试</div>
<script>
var myDiv = document.getElementById('myDiv');
function outputAttributes(element) {
var pairs = new Array(),
attrName,
attrValue,
i,
len;
for (i = 0, len = element.attributes.length; i < len; i++) {
attrName = element.attributes[i].nodeName;
attrValue = element.attributes[i].nodeValue;
pairs.push(attrName + "\"" + attrValue + "\"");
}
return pairs;
}
console.log(outputAttributes(myDiv));
</script>
注意:
- 针对 attributes 对象中的特性,不同浏览器返回的顺序不同。
- IE7 及更早的版本会返回 HTML 元素中所有可能的特性,包括没有指定的特性。而每个特性节点有一个 specified 属性,如果这个属性值为 true,则表示在元素中指定了该特性;如果这个属性值为 false,则表示在元素中没有指定该特性。
5.创建元素
使用 document.createElement() 方法可以创建新元素,该方法只接受一个参数:要创建元素的标签名。
在使用 createElement() 方法创建新元素的同时,也为新元素设置了 ownerDocument 属性。
在 IE 中可以为这个方法传入完整的元素标签,也可以包含属性。
6.元素的子节点
元素可以有任意树木数目的子节点和后代节点,因为元素可以是其他元素的子节点。元素的 childNodes 属性中包含了它的所有子节点,这些子节点可能是元素、文本节点、注释或处理指令。
10.1.4、Text 类型
文本节点由 Text 类型表示,包含的是可以照字面解释的纯文本内容。纯文本中可以包含转义后的 HTML 字符,但不能包含 HTML 代码。Text 节点具有如下特征:
- nodeType 的值为 3;
- nodeName 的值为 "#text";
- nodeValue 的值为节点所包含的文本;
- parentNode 是一个 Element;
- 不支持(没有)子节点。
可以通过 nodeValue 属性或 data 属性访问 Text 节点中包含的文本,这两个属性中包含的值相同。
可以使用下列方法操纵节点中的文本:
- appendData(text):将 text 添加到节点的末尾;
- deleteDate(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 之前处的字符串。
此外,文本节点还有一个 length 属性,保存着节点中字符的数目。且 nodeValue.length = data.length。
默认情况下,每个可以包含内容的元素最多只能有一个文本节点,而且必须确实有内容存在。
<div id="text">there is a boy.</div>
<script>
var text = document.getElementById('text').firstChild;
console.log(text.nodeValue.length); // 15
console.log(text.nodeType); // 3
console.log(text.nodeName); // "#text"
console.log(text.nodeValue); // "there is a boy."
console.log(text.parentNode); // div
text.deleteData(0, 5);
console.log(text.nodeValue); // " is a boy."
text.insertData(0, 'there');
console.log(text.nodeValue); // "there is a boy."
text.replaceData(0, 5, 'abcdefg');
console.log(text.nodeValue); // "abcdefg is a boy."
text.splitText(4);
console.log(text.nodeValue); // "abcd"
console.log(text.substringData(0, 1)); // "a"
</script>
1.创建文本节点
document.createTextNode():接受一个参数——要插入节点中的文本。
注意:一般情况下,每个元素只有一个文本子节点。如果两个文本节点是同胞节点,那么这两个节点中的文本就会连起来显示,中间不会有空格。
<script>
var div = document.createElement('div');
div.className = 'addText';
var oneTextNode = document.createTextNode('hello world!');
div.appendChild(oneTextNode);
var anotherTextNode = document.createTextNode('google!');
div.appendChild(anotherTextNode);
document.body.appendChild(div);
</script>
2.规范化文本节点
相邻文本节点合并:在一个包含两个或多个文本节点的父元素上调用 normalize() 方法,会将所有文本节点合并成一个节点。
<script>
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);
console.log(element.childNodes.length); // 2
element.normalize();
console.log(element.childNodes.length); // 1
console.log(element.firstChild.nodeValue); // 'Hello world!Yippee!'
</script>
注意:浏览器在解析文档时永远不会创建相邻的文本节点。这种情况只会在作为执行 DOM 操作的结果出现。
3.分割文本节点
splitText():分割文本节点,原来的文本节点将包含从开始到指定位置之前的内容,新文本节点将包含剩下的文本。
10.1.5、Comment 类型
注释在 DOM 中是通过 Comment 类型来表示的。Comment 节点具有下列特征:
- nodeType 的值为 8;
- nodeName 的值为 "#comment";
- nodeValue 的值为注释的内容;
- parentNode 可能是 Document 或 Element;
- 不支持(没有)子节点。
Comment 类型与 Text 类型继承自相同的基类,因此它拥有除 splitText() 之外的所有字符串操作方法。
10.1.6、 CDATASection 类型
CDATASection 类型继承自 Text 类型,因此拥有除 splitText() 之外的所有字符串操作方法。CDATASection 节点具有下列特征:
- nodeType 的值为 4;
- nodeName 的值为 "#cdata-section";
- nodeValue 的值是 CDATA 区域中的内容;
- parentNode 可能是 Document 或 Element;
- 不支持(没有)子节点。
参考文献
[1]《JavaScript高级程序设计(第3版)》