JavaScript高级程序设计之DOM 扩展之HTML5之插入标记第11.3.6讲

虽然DOM 为操作节点提供了细致入微的控制手段,但在需要给文档插入大量新HTML 标记的情况
下,通过DOM 操作仍然非常麻烦,因为不仅要创建一系列DOM 节点,而且还要小心地按照正确的顺
序把它们连接起来。相对而言,使用插入标记的技术,直接插入HTML 字符串不仅更简单,速度也更

快。以下与插入标记相关的DOM 扩展已经纳入了HTML5 规范。

1. innerHTML 属性
在读模式下,innerHTML 属性返回与调用元素的所有子节点(包括元素、注释和文本节点)对应
的HTML 标记。在写模式下,innerHTML 会根据指定的值创建新的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>元素来说,它的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>
但是,不同浏览器返回的文本格式会有所不同。IE 和Opera 会将所有标签转换为大写形式,而Safari、
Chrome 和Firefox 则会原原本本地按照原先文档中(或指定这些标签时)的格式返回HTML,包括空格
和缩进。不要指望所有浏览器返回的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>
元素并不会执行其中的脚本。IE8 及更早版本是唯一能在这种情况下执行脚本的浏览器,但必须满足一
些条件。一是必须为<script>元素指定defer 属性,二是<script>元素必须位于(微软所谓的)“有
作用域的元素”(scoped element)之后。<script>元素被认为是“无作用域的元素”(NoScope element),
也就是在页面中看不到的元素,与<style>元素或注释类似。如果通过innerHTML 插入的字符串开头
就是一个“无作用域的元素”,那么IE 会在解析这个字符串前先删除该元素。换句话说,以下代码达不
到目的:
div.innerHTML = "<script defer>alert('hi');<\/script>"; //无效
此时,innerHTML 字符串一开始(而且整个)就是一个“无作用域的元素”,所以这个字符串会变
成空字符串。如果想插入这段脚本,必须在前面添加一个“有作用域的元素”,可以是一个文本节点,
也可以是一个没有结束标签的元素如<input>。例如,下面这几行代码都可以正常执行:

div.innerHTML = "_<script defer>alert('hi');<\/script>";
div.innerHTML = "<div> </div><script defer>alert('hi');<\/script>";
div.innerHTML = "<input type=\"hidden\"><script defer>alert('hi');<\/script>";
第一行代码会在<script>元素前插入一个文本节点。事后,为了不影响页面显示,你可能需要移
除这个文本节点。第二行代码采用的方法类似,只不过使用的是一个包含非换行空格的<div>元素。如
果仅仅插入一个空的<div>元素,还是不行;必须要包含一点儿内容,浏览器才会创建文本节点。同样,
为了不影响页面布局,恐怕还得移除这个节点。第三行代码使用的是一个隐藏的<input>域,也能达到
相同的效果。不过,由于隐藏的<input>域不影响页面布局,因此这种方式在大多数情况下都是首选。
大多数浏览器都支持以直观的方式通过innerHTML 插入<style>元素,例如:
div.innerHTML = "<style type=\"text/css\">body {background-color: red; }</style>";
但在IE8 及更早版本中,<style>也是一个“没有作用域的元素”,因此必须像下面这样给它前置
一个“有作用域的元素”:
div.innerHTML = "_<style type=\"text/css\">body {background-color: red; }</style>";
div.removeChild(div.firstChild);
并不是所有元素都支持innerHTML 属性。不支持innerHTML 的元素有:<col>、<colgroup>、
<frameset>、<head>、<html>、<style>、<table>、<tbody>、<thead>、<tfoot>和<tr>。此
外,在IE8 及更早版本中,<title>元素也没有innerHTML 属性。

Firefox 对在内容类型为application/xhtml+xml 的XHTML 文档中设置innerHTML
有严格的限制。在XHTML 文档中使用innerHTML 时,XHTML 代码必须完全符合
要求。如果代码格式不正确,设置innerHTML 将会静默地失败。
 

无论什么时候,只要使用innerHTML 从外部插入HTML,都应该首先以可靠的方式处理HTML。
IE8 为此提供了window.toStaticHTML()方法,这个方法接收一个参数,即一个HTML 字符串;返回
一个经过无害处理后的版本——从源HTML 中删除所有脚本节点和事件处理程序属性。下面就是一个
例子:

var text = "<a href=\"#\" οnclick=\"alert('hi')\">Click Me</a>";
var sanitized = window.toStaticHTML(text); //Internet Explorer 8 only
alert(sanitized); //"<a href=\"#\">Click Me</a>"
这个例子将一个HTML 链接字符串传给了toStaticHTML()方法,得到的无害版本中去掉了
onclick 属性。虽然目前只有IE8 原生支持这个方法,但我们还是建议读者在通过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>元素。
支持outerHTML 属性的浏览器有IE4+、Safari 4+、Chrome 和Opera 8+。Firefox 7 及之前版本都不
支持outerHTML 属性。

3. insertAdjacentHTML()方法
插入标记的最后一个新增方式是insertAdjacentHTML()方法。这个方法最早也是在IE 中出现的,
它接收两个参数:插入位置和要插入的HTML 文本。第一个参数必须是下列值之一:
"beforebegin",在当前元素之前插入一个紧邻的同辈元素;
"afterbegin",在当前元素之下插入一个新的子元素或在第一个子元素之前再插入新的子元素;
"beforeend",在当前元素之下插入一个新的子元素或在最后一个子元素之后再插入新的子元素;
"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>");
支持insertAdjacentHTML()方法的浏览器有IE、Firefox 8+、Safari、Opera 和Chrome。

4.
内存与性能问题
使用本节介绍的方法替换子节点可能会导致浏览器的内存占用问题,尤其是在IE 中,问题更加明
显。在删除带有事件处理程序或引用了其他JavaScript 对象子树时,就有可能导致内存占用问题。假设
某个元素有一个事件处理程序(或者引用了一个JavaScript 对象作为属性),在使用前述某个属性将该元
素从文档树中删除后,元素与事件处理程序(或JavaScript 对象)之间的绑定关系在内存中并没有一并
删除。如果这种情况频繁出现,页面占用的内存数量就会明显增加。因此,在使用innerHTML、
outerHTML 属性和insertAdjacentHTML()方法时,最好先手工删除要被替换的元素的所有事件处理
程序和JavaScript 对象属性(第13 章将进一步讨论事件处理程序)。
不过,使用这几个属性——特别是使用innerHTML,仍然还是可以为我们提供很多便利的。一般
来说,在插入大量新HTML 标记时,使用innerHTML 属性与通过多次DOM 操作先创建节点再指定它
们之间的关系相比,效率要高得多。这是因为在设置innerHTML 或outerHTML 时,就会创建一个HTML
解析器。这个解析器是在浏览器级别的代码(通常是C++编写的)基础上运行的,因此比执行JavaScript
快得多。不可避免地,创建和销毁HTML 解析器也会带来性能损失,所以最好能够将设置innerHTML
或outerHTML 的次数控制在合理的范围内。例如,下列代码使用innerHTML 创建了很多列表项:

for (var i=0, len=values.length; i < len; i++){
ul.innerHTML += "<li>" + values[i] + "</li>"; //要避免这种频繁操作!!
}
这种每次循环都设置一次innerHTML 的做法效率很低。而且,每次循环还要从innerHTML 中读
取一次信息,就意味着每次循环要访问两次innerHTML。最好的做法是单独构建字符串,然后再一次
性地将结果字符串赋值给innerHTML,像下面这样:

var itemsHtml = "";
for (var i=0, len=values.length; i < len; i++){
itemsHtml += "<li>" + values[i] + "</li>";
}
ul.innerHTML = itemsHtml;


这个例子的效率要高得多,因为它只对innerHTML 执行了一次赋值操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值