插入标记
虽然DOM为操作节点提供了细致入微的控制手段,但在需要给文档插入大量新HTML标记的情况下,通过DOM操作仍然很麻烦,因为不仅要创建一系列DOM节点,而且还要小心地按照正确的顺序把它们连接起来。相对而言,使用插入标记的技术,直接插入HTML字符串不仅更简单,速度也更快。与插入标记相关的DOM扩展已经纳入了HTML5规范。
1.innerHTML属性
在读模式下,innerHTML属性返回与调用元素的所有子节点(包括元素、注释和文本节点)对应的HTML标记。在写模式下,innerHTML会根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点。
读模式
语法格式:元素名.innerHTML
<div id="content">
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
对于上面的<div>
元素来说,它的innerHTML属性会返回如下字符串。
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
但是,不同浏览器返回的文本格式会有所不同。不要指望所有浏览器返回的innerHTML值完全相同。
读模式
语法格式:元素名.innerHTML = 值(文本或标签)
在写模式下,innerHTML的值会被解析为DOM树,替换调用元素原来的所有子节点。因为它的值被认为是HTML,所以其中的所有标签都会按照浏览器处理HTML的标准方式转换为元素(同样,这里的转换结果也因浏览器而异)。如果设置的值仅是文本而没有HTML标签,那么结果就是设置纯文本。
div.innerHTML = "Hello world!";
为innerHTMl设置的包含HTML的字符串值与解析后innerHTML的值大不相同。
div.innerHTML = "Hello & welcome, <b>\"reader\"!</b>";
以上操作得到的结果如下:
<div id="content">Hello & welcome, <b>"reader"!</b></div>
设置了innerHTML之后,可以像访问文档中的其他节点一样访问新创建的节点。
注:为innerHTML设置HTML字符串后,浏览器会将这个字符串解析为相应的DOM树。因此,设置了innerHTML之后,再从中读取HTML字符串,会得到与设置时不一样的结果。原因在于返回的字符串是根据原始HTML字符串创建的DOM树经过序列化之后的结果。
使用innerHTML属性也有一些限制。比如,在大多数浏览器中,通过innerHTML插入<script>
元素并不会执行其中的脚本。
大多数浏览器都支持以直观的方式通过innerHTML插入<style>
元素:
div.innerHTML = "<style type=\"text/css\">body {background-color: red; }</style>";
并不是所有元素都支持innerHTML属性。不支持innerHTML的元素有:<col>
、<colgroup>
、<frameset>
、<head>
、<html>
、<style>
、<table>
、<tbody>
、<thead>
、<tfoot>
、和<tr>
。此外在IE8及更早版本中,<title>
元素也没有innerHTML属性。
2.outerHTML属性
在读模式下,outerHTML返回调用它的元素及所有子节点的HTML标签。 在写模式下,outerHTML会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换调用元素。
<div id="content">
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
如果在<div>
元素上调用outerHTML,会返回与上面相同的代码,包括<div>
本身。不过,由于浏览器解析和解释HTML标记的不同,结果也可能有所不同。(这里的不同与使用innerHTML属性时存在的差异性质是一样的。)
使用outerHTML属性以下面这种方式设置值:
div.outerHTML = "<p>This is a paragraph.<P>";
这行代码完成的操作与下面这些DOM脚本代码一样:
var p = document.createElement("p");
p.appendChild(document.createTextNode("This is a paragraph."));
div.parentNode.replaceChild(p, div);
结果,就是新创建的<p>
元素会取代DOM树中的<div>
元素。
3.insertAdjacentHTML()方法
插入标记的最后一个新增方式是insertAdjacentHTML()方法。它接收2个参数:插入位置和要插入的HTML文本。第一个参数必须是下列值之一:
1.“beforebegin”,在当前元素之前插入一个紧邻的同辈元素。
2.“afterbegin”,在当前元素之下插入一个新的子元素或在第一个子元素之前再插入新的子元素。
3.“beforeend”,在当前元素之下插入一个新的子元素或在最后一个子元素之后再插入新的子元素。
4.“afterend”,在当前元素之后插入一个紧邻的同辈元素。
注:这些值都必须是小写形式。第二个参数是一个HTML字符串(与innerHTML和outerHTML的值相同),如果浏览器无法解析该字符串,就会抛出错误。
//作为前一个同辈元素插入
element.insertAdjacentHTML("beforebegin", "<p>Hello world!</p>");
//作为第一个子元素插入
element.insertAdjacentHTML("afterbegin", "<P>Hello world!</p>");
//作为最后一个子元素插入
element.insertAdjacentHTML("beforeend", "<P>Hello world!</p>");
//作为后一个同辈元素插入
element.insertAdjacentHTML("afterend", "<P>Hello world!</p>");
4.内存与性能问题
使用上述介绍的方法替换子节点可能会导致浏览器的内存占用问题,尤其是在IE中,问题更加明显。在删除带有事件处理程序或引用了其他JavaScript对象子树时,就有可能导致内存占用问题。假设某个元素有一个事件处理程序(或者引用了一个JavaScript对象作为属性),在使用前述某个属性将该元素从文档树中删除后,元素与事件处理程序(或JavaScript对象)之间的绑定关系在内存中并没有一并删除。如果这种情况频繁出现,页面占用的内存数量就会明显增加。因此,在使用innerHTML、outerHTML属性和insertAdjacentHTML()方法时,最好先手工删除要被替换的元素的所有事件处理程序和JavaScript对象属性。
不过,使用这几个属性----特别是使用innerHTML,仍然还是可以为我们提供很多便利的。一般来说,在插入大量新HTML标记时,使用innerHTML属性与通过多次DOM操作先创建节点再指定它们之间的关系相比,效率要高得多。这是因为在设置innerHTML或outerHTML时,就会创建一个HTML解析器。这个解析器是在浏览器级别的代码(通常是C++编写的)基础上运行的,因此比执行JavaScript快得多。不可避免地,创建和销毁HTML解析器也会带来性能损失,所以最好能够将设置innerHTML或outerHTML的次数控制在合理的范围内。
for(var i=0, len=values.length; i<len; i++) { //使用innerHTML创建了很多列表项,
ul.innerHTML += "<li>" + values[i] + "</li>"; //要避免这种频繁操作!!!
这种每次循环都设置一次innerHTML的做法效率太低。而且,每次循环还要从innerHTML中读取一次信息,就意味着每次循环要访问2次innerHTML。最好的做法是单独建构字符串,然后再一次性地将结果字符串赋值给innerHTML:
var itemsHTML = "";
for (var i=0, len=values.length; i<len; i++) {
itemsHTML += "<li>" + values[i] + "</li>";
}
ul.innerHTML = itemsHTML;
这样效率要高得多,因为只对innerHTML执行了一次赋值操作。
插入文本
插入文本的属性innerText和outerText没有被纳入HTML5规范。
1.innerText属性
通过innerText属性可以操作元素中包含的所有文本内容,包括子文档树中的文本。在通过innerText读取值时,它会按照由浅入深的顺序,将子文档树中的所有文本拼接起来。在通过innerText写入值时,结果会删除元素的所有子节点,插入包含相应文本值的文本节点。
<div id="content">
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
对<div>
元素而言,其innerHTML属性会返回下列字符串:
This is a paragraph with a list following it.
Item 1
Item 2
Item 3
由于不同浏览器处理空白符的方式不同,因此输出的文本可能会也可能不会包含原始HTML代码中的缩进。
使用innerText属性设置这个<div>
元素的内容,只需一行代码:
div.innerText = "Hello world!";
执形这行代码后,页面的HTML代码就会变成如下所示:
<div id="content">Hello world!</div>
设置innerText属性移除了先前存在的所有子节点,完全改变了DOM树。此外,设置innerText属性的同时,也对文本中存在的HTML语法字符(小于号、大于号、引号及和号)进行了编码。
div.innerText = "Hello & welcome, <b>\"reader\"!</b>";
运行以上代码后,会得到如下所示结果:
<div id="content">Hello & welcome, <b>"reader"!</b></div>
设置innerHTML永远只会生成当前节点的一个子文本节点,而为了确保只生成一个子文本节点,就必须要对文本进行HTML编码。利用这一点,可以通过innerHTML属性过滤掉HTML标签。方法是将innerText设置为等于innerText,这样就可以去掉所以HTML标签。
div.innerText = div.innerText
执行这行代码后,就用原来的文本内容替换了容器元素中的所有内容(包括子节点、因而也就去掉了HTML标签)。
Firefox不支持innerText属性,但支持作用类似的textContent属性。textContent是DOM Level3规定的一个属性。为了确保跨浏览器兼容,有必要编写一个类似于下面的函数来检测可以使用哪个属性。
function getInnerText(element) {
return (typeof element.textContent == "string") ? element.textContent : element.innerText;
}
function setInnerText(element, text) {
if(typeof element.textContent == "string") {
element.textContent = text;
} else {
element.innerText = text;
}
}
setInnerText(div, "Hello world!");
alert(getInnerText(div)); //"Hello world!"
使用这两个函数可以确保在不同的浏览器中使用正确的属性。
注:实际上,innerText与textContent返回的内容并不完全一样。比如,innerText会忽略行内的样式和脚本,而textContent则会像返回其他文本一样返回行内的样式和脚本代码。避免跨浏览器兼容问题的最佳途径,就是从不包含行内样式或行内脚本的DOM子树副本或DOM片段中读取文本。
2.outerText属性
除了作用范围扩大到了包含调用它的节点之外,outerText与innerText基本上没有多大区别。在读取文本值时,outerText与innerText的结果完全一样。但在写模式下,outerText就完全不同了:outerText不只是替换调用它的元素的子节点,而是会替换整个元素(包括子节点)。
div.outerText = "Hello world!";
这行代码实际上相当于如下两行代码:
var text = document.createTextNode("Hello world!");
div.parentNode.replaceChild(text, div);
本质上,新的文本节点会完全取代调用outerText的元素。此后,该元素就从文档中被删除,无法访问。
由于这个属性会导致调用它的元素不存在,因此并不常用。建议尽可能不要使用这个属性。