DOM操作技术
DOM(Document Object Model,文档对象模型)是针对HTML和XML文档的一个API。DOM描述了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。
节点层次
DOM可以将任何HTML或XML文档描绘成一个由多层节点构造的结构。节点分为几种不同的类型,每种类型分别表示文档中不同的信息及(或)标记。每个节点都拥有各自的特点、数据和方法,另外也与其他节点存在某种关系。节点之间的关系构成了层次,而所有页面标记则表现为一个以特写节点为根节点的树形结构。
html
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<p>Hello world!</p>
</body>
</html>
Node类型
DOM1级定义了一个Node接口,该接口将由DOM中的所有节点类型实现。这个Node接口在JavaScript中是作为Node类型实现的;除IE之外,在其他所有浏览器中都可以访问到这个类型。JavaScript中的所有节点类型都继承自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)。
通过比较上面这些常量,可以很容易地确定节点的类型。
js
if (someNode.nodeType === Node.ELEMENT_NODE) { // 在IE中无效
alert('Node is an element.');
}
由于IE没有公开Node类型的构造函数,因此上面的代码在IE中会导致错误,为了确定跨浏览器兼容,最好还是将nodeType属性
与数字值进行比较。
js
if (someNode.nodeType === 1) {
alert('Node is an element.');
}
并不是所有节点类型都受到Web浏览器的支持。开发人员最常用的就是元素节点和文本节点。
nodeName和nodeValue属性
要了解节点的具体信息,可以使用nodeName
和nodeValue
这两个属性。这两个属性的值完全取决于节点的类型。
js
if (someNode.nodeType === 1) {
value = someNode.nodeName; // nodeName的值是元素的标签名
// 对于元素节点,nodeValue的值始终为null
}
节点关系
文档中所有的节点之间都存在这样或那样的关系。节点之间的各种关系可以用传统的家族关系来描述,相当于把文档树比喻成家谱。
子节点
每个节点都有一个childNodes属性
,其中保存着一个NodeList对象
。NodeList
是一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。请注意,虽然可以通过方括号语法来访问NodeList
的值,而且这个对象也有length属性
,但它并不是Array
的实例。NodeList对象
的独特之处在于,它实际上是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反映在NodeList对象
中。
js
var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes[1];
var count = someNode.childNodes.length;
父节点
每个节点都有一个parentNode属性
,该属性指向文档树中的父节点。包含在childNodes
列表中的所有节点都具有相同的父节点,因此它们的parentNode属性
都指向同一个节点。
同胞节点
包含在childNodes
列表中的每个节点相互之间都是同胞节点。通过使用列表中每个节点的previousSibling
和nextSibling
属性,可以访问同一列表中的其他节点。列表中第一个节点的previourSibling属性
的值为null,而列表中最后一个节点的nextSibling属性
的值也为null。
js
if (someNode.nextSibling === null) {
alert('这是最后一个节点');
} else if (someNode.previousSbiling === null) {
alert('这是第一个节点');
}
firstChild和lastChild
父节点与其第一个和最后一个子节点也存在特殊关系。父节点的firstChild
和lastChild
属性分别指向其childNodes
列表中的第一个和最后一个节点。
在反映这些关系的所有属性当中,childNodes属性
与其他属性相比更方便一些,因为只须使用简单的关系指针,就可以通过它访问文档树中的任何节点。
hasChildNodes()
另外,hasChildNodes()
也是一个非常有用的方法,这个方法可以检测是否含有子节点。这是比查询childNodes.length
更简单的方法。
ownerDocument
所有节点都有一个ownerDocument
属性,该属性指向表示整个文档的文档节点。这种关系表示的是任何节点都属于它所在的文档,任何节点都不能同时存在于两个或更多个文档中。通过这个属性,我们可以不必在节点层次中通过层层回溯到达顶端,而是可以直接访问文档节点。
操作节点
因为关系指针都是只读的,所以DOM提供了一些操作节点的方法。
appendChild()
appendChild()
用于向childNodes列表
的末尾添加一个节点。并且返回新增的节点。
js
var returnedNode = someNode.appendChild(newNode);
alert(returnedNode === newNode); // true
alert(returnedNode === someNode.lastChild); // true
insertBefore()
insertBefore()
会在指定参照的节点前面插入一个节点。这个方法接受两个参数:要插入的节点和作为参照的节点。如果参照节点是null,则insertBefore()
与appendChild()
执行相同的操作。
js
var returnedNode = someNode.insertBefore(newNode, null);
alert(newNode === someNode.lastChild); // true
var returnedNode = someNode.insertBefore(newNode, someNode.firstChild);
alert(returnedNode === newNode); // true
alert(returnedNode === someNode.firstChild); // true
var returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
alert(newNode === someNode.childNodes[someNode.childNodes.length - 2]); // true
replaceChild()
replaceChild()
接受两个参数:要插入的节点和要替换的节点。
js
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
在使用replaceChild()
插入一个节点时,该节点的所有关系指针都会从被它替换的节点复制过来。尽管从技术上讲,被替换的节点仍然还在文档中,但它在文档已经没有了自己的位置。
removeChild()
removeChild()
接受一个参数:要移除的节点。被移除的节点将成为这个方法的返回值。
js
var formerFirstChild = someNode.removeChild(someNode.firstChild);
其他方法
cloneNode()
有两个方法是所有类型的节点都有的。第一个就是cloneNode()
,用于创建调用这个方法的节点的一个完全相同的副本。cloneNode()
接受一个布尔值参数,表示是否执行深复制。在参数为true的情况下,执行深复制,也就是复制节点及其整个子节点树;在参数为false的情况下,执行浅复制,即只复制节点本身。复制后返回的节点副本属于文档所有,但并没有为它指定父节点。因此,这个副本就成了一个“孤儿”,除非把它们添加到文档中。
normalize()
normalize()
这个方法唯一的作用就是处理文档树中的文本节点。由于解析器的实现或DOM操作等原因,可能会出现文本节点不包含文本,或者接连出现两个文本节点的情况。当某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到了空文本节点,则删除它;如果找到相邻的文本节点,则将它们合并为一个文本节点。
Document类型
JavaScript通过Document类型表示文档。在浏览器中,document对象是HTMLDocument(继承自Document类型)的一个实例,表示整个HTML页面。而且,document对象
是window对象
的一个属性,因此可以将其作为全局对象来访问。Document节点具有下列特征:
- nodeType的值为9;
- nodeName的值为"#document";
- nodeValue的值为null;
- parentNode的值为null;
- ownerDocument的值为null;
- 其子节点可能是一个DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction或Comment。
Document类型可以表示HTML页面或者其他基于XML的文档。不过,最常见的应用还是作为HTMLDocument实例的document对象
。
文档的子节点
虽然DOM标准规定Document节点的子节点可以是DocumentType、Element、ProcessingInstruction或Comment,但还有两个内置的访问其子节点的快捷方式。第一个就是documentElement属性
,这个属性始终指向HTML页面中的<html>元素
。另一个就是通过childNodes列表
访问文档元素,但通过documentElement属性
则能更快捷、更直接地访问该元素。
js
var html = document.documentELement;
alert(html === document.childNodes[0]); // true
document对象
还有一个body属性
,直接指向<body>元素
。
js
var body = document.body;
Document另一个可能的子节点是DocumentType。通常将<!DOCTYPE>
标签看成一个与文档其他部分不同的实例,可以通过doctype属性
来访问它的信息。
js
var doctype = document.doctype;
浏览器对document.doctype
的支持差别很大。
- IE8及之前版本:如果存在文档类型声明,会将其错误地解释为一个注释并把它当作Comment节点;而
document.doctype
的值始终为null。 - IE9+及Firefox:如果存在文档类型声明,会将其作为文档的第一个子节点;
document.doctype
是一个DoucmentType节点,也可以通过document.firstChild
或document.childNodes[0]
访问同一个节点。 - Safari、Chrome和Opera:如果存在文档类型声明,则将其解析,但不作为文档的子节点。
document.doctype
是一个DocumentType节点,但该节点不会出现在document.childNodes
中。
由于浏览器对document.doctype
的支持不一致,因此这个属性的用处很有限。
从技术上说,出现在<html>
元素外部的注释应该算是文档的子节点。然而,不同的浏览器在是否解析这些注释以及能否正确处理它们等方面,也存在很大差异。
html
<!-- 这是第一条注释 -->
<html>
<body>
</body>
</html>
<!-- 这是第二条注释 -->
- IE8及之前版本、Safari3.1及更高版本、Opera和Chrome只为第一条注释创建节点,不为第二条注释创建节点。结果,第一条注释就会成为
document.childNodes
中的第一个子节点。 - IE9及更高版本会将两条注释创建为
document.childNodes
中的一个注释节点。 - Firefox以及Safari3.1之前的版本会完全忽略这两条注释。
同样,浏览器间的这种不一致性也导致了位置<html>
元素外部的注释没什么卵用。
多数情况下,我们都用不着在document对象
上调用appendChild()
、removeChild()
和replaceChild()
方法,因为文档类型(如果存在的话)是只读的,而且它只能有一个元素子节点(该节点通常早就已经存在了)。
文档信息
document对象
还有一些标准的Document对象
所没有的属性。这些属性提供了document对象
所表现的网页的一些信息。
title
title属性
包含着<title>
元素中的文本——显示在浏览器窗口的标题或标签页上。
js
// 取得文档标题
var title = document.title;
// 设置文档标题
document.title = 'New Page';
URL、domain和referrer
URL属性
包含页面完整的URL,domain属性
中只包含页面的域名,而referrer属性
中则保存着来源页面的URL地址,如果没有来源页面的情况,referrer属性
中可能会包含空字符串。所有的这些信息都存在于请求的HTTP头部,只不过是通过这些属性让我们能够在JavaScript中访问它们而已。
js
// 取得完整的URL
var url = document.URL;
// 取得域名
var domain = document.domain;
// 取得来源页面的URL
var referrer = document.referrer;
URL
与domain
是相互关联的。
在这3个属性中,只有domain
是可以设置的。但由于安全方面的限制,不能将这个属性设置为URL
中不包含的域。
js
// 假设页面来自blog.humandetail.com
document.domain = 'humandetail.com'; // OK
document.domain = 'baidu.com'; // ERROR
浏览器对domain属性
还有一个限制,即如果域名一开始是“松散的”(loose),那么不能将它再设置为“紧绷的”(tight)。
js
// 假设页面来自blog.humandetail.com
document.domain = 'humandetail.com'; // 松散的(成功)
document.domain = 'blog.humandetail.com'; // 紧绷的(失败)
查找元素
getElementById()
getElementById()
接收一个参数:要取得的元素的ID。如果找到相应的元素则返回该元素,否则返回null。注意,这里的ID必须与页面中的元素的id严格匹配,包括大小写。
html
<div id="myDiv"></div>
js
var myDiv = document.getElementById('myDiv');
getElementsByTagName()
getElementsByTagName()
接收一个参数:要取得的元素的标签名,而返回的是包含零个或多个元素的NodeList
。
在HTML文档中,这个方法会返回一个HTMLCollection对象
,作为一个“动态”集合,该对象与NodeList对象
非常类似。
js
var imgs = document.getElementsByTagName('img');
与NodeList对象
类似,可以使用方括号语法或item()
方法来访问里面的项。而这个对象中的元素数量则可以通过其length属性
取得。
js
alert(imgs.length);
alert(imgs[0].src);
alert(imgs.item(0).src);
HTMLCollection对象
还有一个方法,叫做namedItem()
,使用这个方法可以通过元素的name
取得集合中的项。
html
<img src="myImage.gif" name="myImage">
js
var imgs = docuemtn.getElementsByTagName('img');
var myImage = imgs.namedItem('myImage');
// 也可以使用索引
var myImage = imgs['myImage'];
对于HTMLCollection
而言,我们可以向方括号中传入数值或字符串形式的索引值。在后台,对数值索引就会调用item()
,而对字符串索引就会调用namedItem()
。
要想取得文档中的所有元素,可以向getElementsByTagName()
传递参数'*'
。
js
var allEl = document.getElementsByTagName('*');
getElementsByName()
getElementsByName()
这个方法是HTMLDocument类型才有的方法。这个方法会返回带有给定name特性
的所有元素。最常使用getElementsByName()
的情况是取得单选按钮。
html
<fieldset>
<legend>Which color do you prefer?</legend>
<ul>
<li>
<input type="radio" name="color" value="red" id="colorRed">
<label for="colorRed">Red</label>
</li>
<li>
<input type="radio" name="color" value="green" id="colorGreen">
<label for="colorGreen">Green</label>
</li>
<li>
<input type="radio" name="color" value="blue" id="colorBlue">
<label for="colorBlue">Blue</label>
</li>
</ul>
</fieldset>
js
var radios = document.getElementsByName('color');
与getElementsByTagName()
类似,getElementsByName()
方法返回一个NodeList
。但,对于这里的单选按钮来说,namedItem()
方法则只会取得第一项(因为每一项的name特性都相同)。
特殊集合
document.anchors
,包含文档中所有带name属性的<a>元素
;document.applets
,包含文档中所有<applets>元素
,因为不再推荐使用<applets>
,所以这个集合已经不建议使用了;document.forms
,包含文档中所有<form>元素
;document.images
,包含文档中所有<img>元素
document.links
,包含文档中所有带href属性的<a>元素
。
DOM一致性检测
由于DOM分为多个级别,也包含多个部分,因此检测浏览器实现了DOM哪些部分就十分必要了。document.implementation属性
就是为此提供相应信息和功能的对象,与浏览器对DOM的实现直接对应。
DOM1级只为document.implementation
规定了一个方法:hasFeature()
,这个方法接受两个参数:要检测的DOM功能的名称及版本号。如果浏览器支持给定名称和版本功能,则该方法返回true。
js
var hasXmlDom = document.implementation.hasFeature('XML', '1.0');
文档写入
有一个document对象
的功能已经存在很多年了,那就是将输出流写入到网页中的能力。这个能力体现在下列4个方法中:write()
、writeln()
、open()
和close()
。其中write
和writeln()
方法都失道寡助一个字符串参数:即要写入到输出流中的文本。write()
会原样写入,而writeln()
则会在字符串的末尾添加一个换行符(\n
)。在页面被加载的过程中,可以使用这两个方向向页面动态地加入内容。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<p>The current date and time is: </p>
<script>
document.write('<strong>' + new Date().toString() + '</strong>')
</script>
</body>
</html>
此外,还可以使用write()
和writeln()
方法动态地包含外部资源。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script></script>
</body>
</html>
注意,如果在文档加载结束后再调用document.write()
,那么输出的内容将会重写整个页面。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<p>这里是一串没什么卵用的文本,因为页面会被重写</p>
<script>
window.onload = function () {
document.write('Hello world!');
}
</script>
</body>
</html>
Element类型
除了Document类型之外,ELement类型就要算是Web编程中最常用的类型了。Element类型用于表现HTML或XML元素,提供了对元素标签名、子节点及特性的访问。Element节点具有以下特征:
- nodeType的值为1;
- nodeName的值为元素的标签名;
- nodeValue的值为null;
- parentNode可能是Document或Element;
- 其子节点可能是Element、Text、Comment、ProcessingInstruction、CDATASection或EntityReference。
要访问元素的标签名,可以使用nodeName属性
,也可以使用tagName属性
;这两个属性会返回相同的值。
html
<div id="myDiv"></div>
js
var div = document.getElementById('myDiv');
alert(div.tagName); // 'DIV'
alert(div.tagName === div.nodeName); // true
在HTML中,标签名始终都以全部大写表示;而在XML(有时候也包括XHTML)中,标签名则始终会与源代码中的保持一致。
js
// 比较之前最好先将标签名转换为相同的大小写形式。
if (element.tagName.toLowerCase() === 'div') {
// ...
}
HTML元素
所有HTML元素都由HTMLElement类型表示,不是直接通过这个类型,也是通过它的子类型来表示。HTMLElement类型直接继承自Element并添加了一些属性。添加的这些属性分别对应于每个HTML元素中都存在的下列标准特性。
- id,元素在文档中的唯一标识符。
- title,有关元素的附加说明信息。
- lang,元素内容的语言代码,很少使用。
- dir,语言的方向,值为“ltr”(left-to-right)或“rtl”(right-to-left),也很少使用。
- className,与元素的class特性对应,即为元素指定的类名。没有将这个属性命名为class是因为class是ECMAScript的保留字。
上述这些属性都可以用来取得或修改相应的特性值。
html
<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>
js
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); // 'ltr'
也可以通过赋值修改对应的每个特性。
js
div.id = 'someOtherId';
div.className = 'ft';
div.title = 'Some other text';
div.lang = 'fr';
div.dir = 'rtl'
取得特性(getAttribute)
每个元素都有一个或多个特性,这些特性的用途是给出相应元素或其内容的附加信息。
getAttribute()
,接受一个特性名作为参数,返回该特性的值,如果给定的特性名不存在,则返回null。
html
<div id="myDiv" class="bd" myTitle="hello world!">
js
var div = document.getElementById('myDiv');
alert(div.getAttribute('id')); // 'myDiv'
alert(div.getAttribute('class')); // 'bd'
alert(div.getAttribute('myTitle')); // 'hello world!'
alert(div.getAttribute('title')); // null
// 对于公认的(非自定义)特性,可以以属性的形式访问
alert(div.id); // 'myDiv'
alert(div.myTitle); // undefined(IE除外)
有两类特殊的特性:style
和onclick
这样的事件处理程序,它们虽然有对应的属性名,但属性的值与通过getAttribute()
返回的值并不相同。
设置特性
与getAttribute()
对应的方法是setAttribute()
,这个方法接受两个参数:要设置的特性名和值。如果特性存在则会替换现有的值;如果特性不存在,则创建该属性并设置相应的值。
js
div.setAttribute('id', 'someOtherId');
div.setAttribute('dir', 'rtl');
因为所有特性都是属性,所以直接给属性赋值可以设置特性的值。不过添加自定义属性不会自动转成为元素的特性。
js
div.id = 'someOtherId';
div.mycolor = 'red'; // 不会自动转换成为元素的特性
删除特性
removeAttribute()
用于彻底删除元素的特性。调用这个方法不仅会清除特性的值,而且也会从元素中完全删除特性。
js
div.removeAttribute('class');
attributes属性
Element类型是使用attributes属性
的唯一一个DOM节点类型。attributes属性
中包含一个NamedNodeMap
,与NodeList
类似,也是一个“动态”的集合。元素的每一个特性都由一个Attr节点
表示,每个节点都保存在NamedNodeMap对象
中。NamedNodeMap对象
拥有下列方法。
getNamedItem(name)
:返回nodeName属性
等于name的节点;removeNamedItem(name)
:从列表中移除nodeName属性
等于name的节点;setNamedItem(node)
:向列表中添加节点,以节点的nodeName属性
作为索引;item(pos)
:返回位于数字pos位置处的节点。
js
// 获取元素的id特性
var id = element.attributes.getNamedItem('id').nodeValue;
// or
var id = element.attributes['id'].nodeValue;
// 设置
element.attributes['id'].nodeValue = 'someOtherId';
调用removeNamedItem()
方法与在元素上调用removeAttribute()
方法效果相同。不会removedNamedItem()
会返回被删除特性的Attr节点
。
js
var oldAttr = element.attributes.removeNamedItem('id');
最后,setNamedItem()
是一个很不常用的方法,通过这个方法可以为元素添加一个特性。
js
element.attributes.setNamedItem(newAttr);
由于attributes
的方法不够方便,因此开发人员更多的会使用getAttribute()
、setAttribute()
和removeAttribute()
方法。
如果想要遍历元素的特性,attributes属性
倒是可以派上用场。以下代码展示如何迭代元素的每一个特性,然后将它构造成name="value" name="value"
这样的字符串格式。
jsx
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.join(' ');
}
创建元素
使用document.createElement()
方法可以创建新元素。这个方法只接受一个参数,即要创建元素的标签名。这个标签名在HTML文档中不区分大小写,而在XML(包括XHTML)文档中,则是区分大小写的。
js
var div = document.createElement('div');
div.id = 'myDiv';
div.className = 'box';
document.body.appendChild(div);
元素的子节点
元素可以有任意数目的子节点和后代节点。元素的childNodes属性
中包含了它的所有子节点。
html
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
如果是IE来解析这段代码,那么<ul>元素
会有3个子节点,分别是3个<li>元素
。但如果在其他浏览器中,<ul>元素
会有7个子节点,包括3个<li>元素
和4个文本节点(表示<li>元素
之间的空白符)。如果像下面这样将元素间的空白符删除,那么所有浏览器都会返回相同数目的子节点。
html
<ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>
如果需要通过childNodes属性
遍历子节点,那么一定不要忘记浏览器间的这一差别。
js
for (var i = 0, len = element.childNodes.length; i < len; i++) {
if (element.childNodes[i].nodeType === 1) { // 确保是元素节点
// ...
}
}
如果想通过某个特定的标签名取得子节点或后代节点该怎么办?实际上,元素也支持getElementsByTagName()
方法。
js
var ul = document.getLementById('myList');
var items = ul.getElementsByTagName('li');
Text类型
文本节点由Text类型表示,包含的可以是照字面解释的文本内容。纯文本中可以包含转义后的HTML字符,但不能包含HTMl代码。Text节点具有以下特征:
- nodeType的值为3;
- nodeName的值为’#text’;
- nodeValue的值为节点所包含的文本;
- parentNode是一个Element;
- 不支持(没有)子节点。
可以通过nodeValue属性
或data属性
访问Text节点中包含的文本,这两个属性中包含的值相同。
使用下列的方法可以操作节点中的文本:
- appendData(text):将text添加到节点的末尾。
- deleteData(offset, count):从offset位置开始删除count个字符。
- insertData(offset, text):在offset位置插入text。
- replcaData(offset, count, text):用text替换从offset位置开始到offset+count位置处的文本。
- splitText(offset):从offset位置将当前文本节点分成两个文本节点。
- substringData(offset, count):提取从offset位置到offset+count位置处的字符串。
除了这些方法之外,文本节点还有一个length属性
,保存着节点中字符的数目,而且nodeValue.lengtht
和data.length
也保存着同样的值。
创建文本节点
可以使用document.createTextNode()
创建新文本节点,这个方法接受一个参数——要插入节点中的文本。作为参数的文本会按照HTML或XMl的格式进行编码。
js
var textNode = document.createTextNode('<strong>Hello</strong>'); // 实际上会插入'<strong>Hello</strong>'
在创建新文本节点的同时,也会为其设置ownerDocument属性
。
一般情况下,每个元素只有一个文本子节点。不过在某些情况下也可能包含多个文本子节点。
js
var element = document.createElement('div');
element.className = 'message';
var textNode = document.createTextNode('Hello world!');
element.appendChild(textNode);
var anotherTextNode = document.createTextNode('Another Text');
element.appendChild(anotherTextNode);
document.body.appendChild(element);
如果两个文本节点是相邻的同胞节点,那么这两个节点的文本会连接起来显示,中间不会有空格。
规范化文本节点
DOM文档中存在相邻的同胞文本节点很容易导致混乱,因为分不清哪个文本节点表示哪个字符串。于是就有了一个将相邻文本节点合并的方法。这个方法是由Node类型定义的(因而在所有节点类型中都存在),名叫normalize()
。
js
// 接上例
alert(element.childNodes.length); // 2
element.normalize();
alert(element.childNodes.length); // 1
alert(element.firstChild.nodeValue); // 'Hello world!Another Text'
分割文本节点
Text类型提供了一个作用与normalize()
相反的方法:splitText()
。这个方法会将一个文本节点分成两个文本节点,即按照指定的位置分割nodeValue值
。原来的文本节点将包含从开始位置到指定位置之前的内容,新文本节点将包含剩下的文本。这个方法会返回一个新文本节点。
js
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); // 'Helllo'
alert(newNode.nodeValue); // ' world'
alert(element.childNodes.length); // 2
Comment类型
注释在DOM中是通过Comment类型来表示的。Comment节点具有下列特征:
- nodeType的值为8;
- nodeName的值为’#comment’;
- nodeValue的值是注释的内容;
- parentNode可能是Document或Element;
- 不支持(没有)子节点。
Comment类型与Text类型继承自相同的基类,因此它拥有除splitText()
之外的所有字符串操作方法。
CDATASection类型
CDATASection类型只针对基于XML的文档,表示的是CDATA区域。与Comment类型类似。CDATASection节点具有以下特征:
- nodeType的值为4;
- nodeName的值为’#cdata-section’;
- nodeValue的值是CDATA区域中的内容;
- parentNode可能是Document或Element;
- 不支持(没有)子节点。
DocumentType类型
DocumentType类型在Web浏览器中并不常用。它具有以下特征:
- nodeType的值为10;
- nodeName的值为doctype的名称;
- nodeValue的值是null;
- parentNode是Document;
- 不支持(没有)子节点。
在DOM1级中,DocumentType对象不能动态创建,而只能通过解析文档代码的方式来创建。支持它的浏览器会把DocumentType对象保存在document.doctype
中。DOM1级描述了DocumentType对象的3个属性:name
(文档类型描述的名称)、entities
(文档类型描述的实体NamedNodeMap对象)和notations
(文档类型描述的符号NamedNodeMap对象)。
DocumentFragment类型
在所有节点类型中,只有DocumentFragment在文档中没有对应的标记。DOM规定文档片段(document fragment)是一种“轻量级”的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源。DocumentFragment节点具有以下特征:
- nodeType的值为11;
- nodeName的值为’#document-fragment’;
- nodeValue的值为null;
- parentNode的值为null;
- 子节点可以是Element、ProcessingInstruction、Comment、Text、CDATASection或EntityReference。
虽然不能把文档版本添加到文档中,但可以将它作为一个“仓库”来使用,即可以在里面保存将来可能会添加到文档中的节点。
可以使用document.createDocumentFragment()
方法来创建文档片段。
js
var fragment = document.createDocumentFragment();
文档版本继承了Node的所有方法,通常用于执行那些针对文档的DOM操作。如果将文档中的节点添加到文档片段中,就会从文档树中移除该节点,也不会从浏览器中再看到该节点。添加到文档片段中的新节点也不属性文档树。可以通过appendChild()
或insertBefore()
将文档片段中的内容添加到文档中。在将文档片段作为参数传递给这两个方法时,只会将文档片段的所有子节点添加到相应位置,文档片段本身永远不会成为文档树的一部分。
Attr类型
元素的特性在DOM中以Attr类型来表示。特性节点具有以下特征:
- nodeType的值为2;
- nodeName的值为特性的名称;
- nodeValue的值为特性的值;
- parentNode的值为null;
- 在HTML中不支持(没有)子节点;
- 在XML中子节点可以是Text或EntityReference。
Attr对象有3个属性:name
、value
和specified
(布尔值,用于区分特性是在代码中指定的,而且是默认的)。
使用document.createAttribute()
关传入特性名称可以创建新的特性节点。
js
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动态添加脚本。
js
var script = document.createElement('srcipt');
script.type = 'text/javascript';
script.src = 'client.js';
document.body.appendChild(script);
// or
var script = document.createElement('srcipt');
script.type = 'text/javascript';
script.appendChild(document.createTextNode('function sayHi () { alert("hi"); }'));
document.body.appendChild(script);
在Firefox、Safari、Chrome和Opera中,这些DOM代码可以正常运行。但在IE中,则会导致IE将<script>
视为一个特殊的元素,不允许DOM访问其子节点。不过,可以使用<script>元素
的text属性
来指定JavaScript代码。
js
var script = document.createElement('srcipt');
script.type = 'text/javascript';
script.text('function sayHi () { alert("hi"); }');
document.body.appendChild(script);
// 兼容性版本
var script = document.createElement('srcipt');
script.type = 'text/javascript';
var code = 'function sayHi () { alert("hi"); }';
try {
script.appendChild(document.createTextNode(code));
} catch (e) {
script.text = code;
}
document.body.appendChild(script);
以这种方式加载的代码会在全局作用域中执行,而且当脚本执行后立即可用。实际上,这样执行代码与全局作用域中把相同的字符串传递给eval()
是一样的。
动态样式
js
var style = document.createElement('style');
style.type = 'text/css';
try {
style.appendChild(document.createTextNode('body { background-color: red }'));
} catch (e) {
style.styleSheet.cssText = 'body { background: red }';
}
document.getElementsByTagName('head')[0].appendChild(style);
操作表格
<table>元素
是HTML中最复杂的结构之一。要想创建表格,一般都必须涉及表格行、单元格、表头等标签。由于涉及的标签多,因而使用核心DOM方法创建和修改表格往往都免不了要编写大量的代码。
为了方便构建表格,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文档时实时运行的查询。例如,下面的代码会导致无限循环:
js
var divs = document.getElementsByTagName('div'),
i,
div;
for (i = 0; i < divs.length; i++) {
div = document.createElement('div');
document.body.appendChild(div);
}
如果想要迭代一个NodeList,最好是使用length属性
初始化第二个变量,然后将迭代器与该变量进行比较。
js
var divs = document.getElementsByTagName('div'),
i,
len,
div;
for (i = 0, len = divs.length; i < len; i++) {
div = document.createElement('div');
document.body.appendChild(div);
}
一般来说,应该尽量减少访问NodeList的次数。因为每次访问,都会运行一次基于文档的查询。