本文承接《DOM扩展:DOM API的进一步增强[总结篇-上]》,继续总结DOM扩展相关的功能和API。
3.6 插入标记
DOM1级中的接口已经提供了向文档中插入内容的接口,但是在给文档插入大量HTML标记的时候操作还是很繁杂的,每次插入一个元素,不仅要调用创建元素和文本节点的接口,还要调用appendChild等向文档中添加元素的接口,而且在添加时还要按照正确的顺序。而如果使用插入标记的方法,直接向文档中插入HTML字符串,由执行环境自动解析HTML字符串并创建相应的节点并添加到文档中,这样的话操作就方便多了。与插入标记相关的DOM扩展总结如下:
3.6.1 innerHTML属性
使用innerHTML可以读取元素所有子节点对应的HTML字符串(包括元素、注释节点和文本);也可以为元素设置子节点的HTML标记,此时会根据指定的值创建新的DOM树,然后用新的DOM树替换元素的子节点。
(1)读取innerHTML属性
以下面的代码为例:
<div id="wrapper">
<p>一个段落在这里</p>
<span>这里是一个span</span>
</div>
我们如果获取div#wrapper的innerHTML值应该就是:
<p>一个段落在这里</p>
<span>这里是一个span</span>
[1]所有的标签都会转为大写
[2]所有的空白节点都会被去掉
仍以上面的代码为了,在IE8及以下的浏览器中返回的内容就是:
<P>一个段落在这里</P><SPAN>这里是一个span</SPAN>
(2)设置innerHTML属性
设置innerHTML属性时,HTML字符串会被解析为DOM树并替换元素的所有子节点。如果传入的字符串不包含任何标签,那么生成的就是文本节点,而如果传入的字符串包含由HTML标签,那么标签就会被解析为相应的HTML元素,这一点与创建Text类型节点时的情况是大不相同的:
我们还是以上面的div#wrapper为例:
如果调用:
div.innerHTML = "<strong>"This is innerHTML"</strong>";
则<strong>会被正确地解析,其中的文本就会被渲染成粗体。
使用innerHTML也存在一些限制:
[1]script标签
在大部分浏览器中,通过innerHTML插入的<script>标签不会被执行,只有IE9及以下的浏览器会执行,但也需要满足一定的条件,首先需要为script添加defer特性,其次<script>标签必须位于"有作用域"的元素之后("有作用域"的元素这里可以理解为会直观显示在页面上的元素,比如script,style这些元素是不会显示在页面上的,而input、div等元素会直接显示在页面上,因此可以称为"有作用域")。还是以上面的div#wrapper为例,有如下代码:
div.innerHTML = "<script>alert('dd')</script>";代码执行后,标签会被插入div中,但脚本在任何浏览器中都不会执行。再来修改一下代码:
div.innerHTML = "<script defer='defer'>alert('dd')</script>";这段代码为innerHTML加入了一个defer特性,但正如上文所讲的,script是一个"没有作用域"的元素,所以在IE9及以下浏览器中,脚本还是不会被执行。这个时候只要在<script>前面添加一个字符串或者其他"有作用域"的元素即可,为了不影响文档的实际内容,一般在script标签之前添加一个隐藏的input元素即可,代码如下:
div.innerHTML = "<input type='hidden'/><script defer='defer'>alert('dd')</script>";这样在IE9及以下的浏览器中,脚本就能够执行了。
注:如果是引入外部javascript文件,那么所有浏览器都不会下载执行js文件和其中的代码
[2]style和link标签
在IE8及以下的浏览器中,如果插入style标签,必须也要跟随在一个"有作用域"的元素之后,因此可以采用<script>一节中介绍的方法。高版本IE及其他浏览器中插入的style样式都可以被执行。
另外,如果使用link标签引入外部样式,那么和style标签的效果是一样的
还有一些元素是不支持innerHTML属性的,包括:<table>、 <thead>、 <tbody>、 <tfoot>、 <tr>、 <style>、 <html>、 <head>、 <frameset>、<col>、<colgroup>
由于innerHTML可以插入HTML标签,那么就涉及到安全问题,最典型的的就是XSS,因此在使用innerHTML时应该对插入的字符串进行安全处理,特别是需要保存到数据库中的字符串代码。比如插入的字符串不应该包含脚本代码(及时在一些浏览器中脚本代码不会执行),不应该包含事件处理程序和外部链接等。IE8中提供了window.toStaticHTML(),可以将传入的字符串进行处理:删除脚本节点和事件处理程序。
3.6.2 outerHTML属性
从字面上
,outerHTML与innerHTML的不同就在于"outer",也就是说,outerHTML属性在读取或写入时除了包含所有子节点,同时也把自己算进去了。我们还是以3.6.1中的div#wrapper为例,读取outerHTML时返回的是:
<div id="wrapper">
<p>一个段落在这里</p>
<span>这里是一个span</span>
</div>
<DIV id=wrapper><P>一个段落在这里</P><SPAN>这里是一个span</SPAN></DIV>
3.6.3 insertAdjacentHTML方法
insertAdjacentHTML方法为元素提供了更加灵活的标记插入方法,它接收两个参数:插入的位置和HTML字符串,其中插入的位置必须是下面几个参数:
"beforebegin":在元素之前插入一个紧邻的同辈元素
"afterbegin":如果元素没有子元素,那么就直接插入;如果元素有子元素,就在第一个子元素之前插入
"beforeend":如果元素没有子元素,那么就直接插入;如果元素有子元素,就在最后一个子元素之后插入
"afterend":在元素之后插入一个紧邻的同辈元素
我们仍以3.6.1中的div为例,调用如下代码:
div.insertAdjacentHTML("beforeend","<p>在结束前插入一个</p>");
console.log(div.innerHTML);
<p>一个段落在这里</p>
<span>这里是一个span</span>
<p>在结束前插入一个</p>
3.6 scrollIntoView()方法
要强制浏览器以某种模式渲染页面,可以通过设置HTTP的X-UA-Compatible头部或设置对应的meta标签来实现,声明方式为:
DOM1级中的接口已经scrollIntoView方法可以在所有的HTML元素上调用,调用这个方法后,浏览器会通过滚动窗口或者某个容器窗口来让元素出现在视口中。
这个方法接收一个布尔型参数,默认为true,如果为true的话,滚动后浏览器会让视口顶部与元素顶部尽可能平齐;如果为false,调用元素会尽可能出现在视口中。
4
专有扩展
不同的浏览器开发商可能会为DOM扩展不同的功能,这些功能有的被吸收进入了标准,正如第3章中所介绍的这些扩展,而有的功能却还没有被纳入标准中,不过这并不代表着这些功能以后也不会被写入标准中,只是目前这些功能还是各浏览器专有的扩展,只能在特定的浏览器中使用。
4.1 文档模式
IE8中引入了文档模式的概念,文档模式决定了我们可以使用哪些功能,也就是说,文档模式决定了我们可以使用哪个级别的CSS,可以使用JavaScript API中的哪些功能以及如何对待文档类型。之后的IE版本都提供了向下兼容的文档模式,比如在IE9中,可选的文档模式有:IE5(以混杂模式渲染页面)、IE7(以IE7的标准模式渲染页面)、IE8(以IE8的标准模式渲染页面,到了IE8,DOM Selectors API、CSS2.1的完整功能以部分CSS3的功能就都可以使用了)、IE9(以IE9的标准模式渲染页面,到了IE9,ES5、CSS3以及更多的HTML5的功能也都可以使用了)
<meta http-equiv="x-ua-compatible" content="IE=IEVersion"/>其中,IEVersion的可能取值有:
[1]edge:始终以最新的文档模式来渲染页面,忽略文档类型声明
[2]EmulateIEXX:XX代表某个IE版本,即如果有文档类型声明,那么就以XX版本的IE标准模式来渲染页面,否则把文档模式设为IE5
[3]XX:XX代表某个IE版本,即忽略文档类型声明,以XX版本的IE标准模式渲染页面
如果没有设置X-UA-Compatible,则浏览器会根据文档类型声明选择最佳的文档模式进行渲染。
document.documentMode可以判断文档当前所处的文档模式,这个属性只有IE支持。
4.2 children属性
IE8及以下的浏览器在处理文本节点的空白节点时与其他浏览器有所不同,因此使用childNodes属性遍历DOM树时还需要判断某个子节点的具体类型,而children属性的出现可以解决这个问题,它只返回一个元素所有的子元素。
注:IE8及以下的浏览器也会返回注释节点。
IE5+,Firefox3.5+,chrome,Opera8+,Safari3+支持children属性。
4.3 contains方法
实际开发中,经常需要判断一个节点是否包含另一个节点,比如我们有如下HTML代码:
<body> <div id="div-1"> <div id="title"> <h1 id="title-h1">标题</h1> <span id="more">更多</span> </div> </div> <div id="div-2"> <div id="content-wrapper"> <div id="block"> <p id="desc">这里是一段描述</p> <img id="img" src=""/> </div> </div> </div> </body>
如果我们想要判断div#div-2是否包含span#more,如果只借助DOM1级的API,那么我们会可能写出如下代码:
var div2 = document.getElementById("div-2"), div1 = document.getElementById("div-1"), more = document.getElementById("more"); alert(nodeContains(div1, more)); alert(nodeContains(div2, more)); function nodeContains(node, childNode){ var result = false; while(childNode != null){ if(childNode === node){ result = true; break; } childNode = childNode.parentNode; } return result; }
代码执行后将分别弹出true和false
注:Firefox9+和其他浏览器都支持contains方法。
有了contains方法之后,我们可以直接使用:
alert(div1.contains(more)); alert(div2.contains(more));
也将分别弹出true和false。
另外,DOM3级中又定义了compareDocumentPosition方法,也可以用来判断一个节点是否包含另一个节点,这里我们不再详解这个方法,可以参考:
4.4 插入文本
3.6节介绍了插入标记相关的属性:innerHTML和outerHTML,并且这两个特性已经被纳入了HTML5标准之中,而另外两个插入文本相关的属性则没有被标准看上,它们就是innerText和outerText。
4.4.1 innerText属性
使用innerText属性可以读取元素内的文本内容,也可以设置元素的文本内容,
(1)读取innerText属性
如果调用Element.innerText属性,会按照由浅到深的顺序,将元素的所有文本节点拼接起来并返回。但是由于不同的浏览器处理空白符的方式不同,因此返回的字符串也可能不包含原始HTML代码中的缩进。有如下代码:
<div id="wrapper">那么调用div#wrapper的innerText属性返回的内容可能是:
<p>一个段落在这里</p>
<span>这里是一个span</span>
</div>
一个段落在这里也可能是:
这里是一个span
一个段落在这里这主要还是由于不同浏览器处理空白符节点的方式不同而导致的。
这里是一个span
(2)设置innerText属性
如果为一个元素设置innerText属性,则意味着删除元素的所有子节点,并将文本节点插入,文本中包含的HTML语法字符都会被转义,还是以上面的div#wrapper为例:
div.innerText = "<strong>Hello</strong>";代码执行后,显示结果为:
支持innerText属性的浏览器有:IE4+,chrome,Opera8+,Safari3+;特别注意的是:firefox不支持innerText,但是有一个类似的textContent属性,另外还支持textContent属性的有:IE9+,chrome,Opera10+,Safari3+
注:textContent和innerText返回的内容有时候是不相同,比如innerText会忽略元素内部所有的脚本标签代码和样式标签代码,但textContent却不会忽略。
4.4.2 outerText属性
与innerText属性相似,在读取时,返回的结果与innerText相同,在设置时,则会将整个节点直接替换掉。