标签:
对DOM的两个主要扩展是Selectors API(选择符API)和HTML5,还有一个不太瞩目的Element Traversal元素遍历规范为DOM添加了一些属性,另外还有一些专有扩展。
选择符API
让浏览器原生支持css查询,原理就是所有实现这一功能的JavaScript库都会写一个基础的CSS解析器,然后再使用已有的DOM方法查询文档并找到匹配的节点。当把这个功能变成原生API后,解析和树查询操作可以在浏览器内部通过编译后的代码来完成,极大改善性能。
Selectors API Level1核心两个方法:querySelector和querySelectorAll。在兼容的浏览器中可通过Document,Element,DocumentFragment类型实例(方法继承自Document.prototype和Element.prototype和DocumentFragment.prototype)调用它们。兼容性如图,反正大部分支持。
Selectors API Level 2规范为Element类型新增了一个方法matchesSelector()。各浏览器支持不统一。
querySelector()方法:接收一个CSS选择符,返回与该模式匹配的第一个元素,如果没有找到返回null。通过Document类型调用querySelector方法时,会在文档元素范围内查找匹配的元素,通过Element类型调用querySelector方法时只会在该元素后代元素范围内查找匹配的元素。如果传入了不被支持的选择符,querySelector()会抛出错误。
//取得类为“button”的第一个图像元素 var img= document.body.querySelector(‘img.button‘);
querySelectorAll()方法:返回的所有而不仅仅是一个元素。这个方法返回的是一个NodeList类型实例而且是静态集合。querySelectorAll优点就是返回的值实际上是带有所有属性和方法的NodeList实例,而其底层实现则类似于一组元素的快照,而非不断对文档进行搜索的动态查询,这样实现可以避免使用NodeList对象通常会引起大多数性能上的问题。如果没有找到匹配的元素,返回空的NodeList实例(类数组)。如果传入了不被支持的选择符,querySelectorAll()会抛错。
//取得所有p元素中的所有strong元素 var strongs = document.querySelectorAll(‘p strong‘);
要取得返回的NodeList实例中的每一个元素,可使用item()方法,也可以使用方括号语法。
var i, len, strong; for(i = 0,len = strongs.length; i<len; i++){ strong = strongs[i];// 或strongs.item(i) strong.className = "test"; }
matchesSelector()方法:接受一个css选择符参数,如果调用元素与该选择符相匹配,返回true。
if(document.body.matchesSelector("body.page1")){ //true }
截至现在三大主流浏览器还没有支持matchesSelector方法,但IE9+msMatchesSelector()支持该方法,FF3.6+通过mozMatchesSelector()支持该方法,Safari5+和Chrome通过webkitMatchesSelector()支持该方法。这些方法均在Element.prototype上。编写一个包装函数兼容:
function matchesSelector(ele, selector){ if(ele.matchesSelector){ return ele.matchesSelector(selector); }else if(ele.msMatchesSelector){ return ele.msMatchesSelector(selector); }else if(ele.mozMatchesSelector){ return ele.mozMatchesSelector(selector); }else if(ele.webkitMatchesSelector){ return ele.webkitMatchesSelector(selector); }else{ throw new Error("Not supported"); } } if(matchesSelector(document.body, ‘body.page1‘)){ //true }
元素遍历
对于元素间空格,<=IE8会忽略HTML元素中文本节点,其他浏览器会返回这个文本节点。这样就导致在使用childNodes和firstChild等属性时行为不一致,Element Traversal元素遍历规范定义了一组属性。元素遍历规范为DOM元素添加添加了以下5个属性:均继承自Element.prototype或Document.prototype或DocumentFragment.prototype。
Element.prototype
Document.prototype只继承这三个方法
DocumentFragment.prototype也只继承这三个方法
跨浏览器遍历某元素所有子元素:
var child = ele.firstChild; while(child!=ele.lastChild){ if(child.nodeType == 1){ //元素节点 } child = child.nextSibling; }
使用Element Traversal代码会更简洁,支持元素遍历方法的浏览器有IE9+,FF3.5+,Safari4+,Chrome,Opera10+
var child = ele.firstElementChild; while(child != ele.lastElementChild){ //元素节点 child = child.nextElementSibling; }
HTML5
HTML5规范围绕如何使用新增标记定义大量JS API,其中一些API与DOM重叠,定义了浏览器应该支持的DOM扩展。
//取得所有类中包含“username”和“current”的元素,类名先后顺序无所谓 var eles = document.getElementsByClassName("username current"); //取得ID为“myDiv”的元素中带有类名“selected”的所有元素 var eles = document.getElementById(‘myDiv‘).getElementsByClassName(‘selected‘);
//删除"user"类 var classNames = ele.className.split(/\s+/); var idx = classNames.indexOf(‘user‘); classNames.splice(idx, 1); ele.className = classNames.join(‘ ‘);
比如添加类名需要通过字符串拼接,而且后续要检测确定不会多次添加相同类名。
HTML5新增一种操作类名的方式,为所有元素添加了classList属性(继承自Element.prototype)。这个classList属性是DOMTokenList的实例,来学习一下这个接口:原型链继承关系为:ele.classList.__proto__->DOMTokenList.prototype->Object.prototype。
add(value):将给定的字符串值添加到列表中,如果值已存在就不添加了。
contains(value):表示列表中是否存在给定的值,如果存在则返回true,否则返回false。
remove(value):从列表中删除给定的字符串,并即时反映在文档中。
toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。并且即时反映到文档中。
有了classList属性,除非你需要全部删除所有类名,或者完全重写元素的class属性,否则也用不到classList属性。
兼容性:不太好
<script type="text/javascript"> document.getElementById(‘a‘).focus(); alert(document.activeElement); alert(document.hasFocus()); </script>
if(document.readyState == "complete"){ //执行操作 }
var head = document.head || document.getElementsByTagName(‘head‘)[0];
div.innerHTML = "<script defer>console.log(‘hi‘)</script>";// 无效
因为此时innerHTML字符串一开始就是一个”无作用域元素“,所以这个字符串会变成空字符串。
解决方案:
//在<script>前面添加一个有作用域的元素 div.innerHTML = "<div> </div><script defer>alert(‘hi‘)<\/script>"; //添加文本节点 div.innerHTML = "_<script defer>alert(‘hi‘)<\/script>"; //添加一个没有结束标签的元素 div.innerHTML = "<input type=\”hidden\“><script defer>alert(‘hi‘)<\/script>"
document.body.innerHTML = "<style type=\"text/css\">body{background:red;}</style>";
但在<=IE8中<style>也是一个一个没有作用域的元素,因此可以通过下面方式给它前置作用域
document.body.innerHTML = "_<script type=\"text/css\">body{background:red;}</style>"
div.outerHTML = "<p>this is a paragraph</p>"; //等价于 var p = document.createElement(‘p‘); p.appendChild(document.createTextNode(‘this is a paragraph‘)); div.parentNode.replaceChild(p, div);
<=FF7不支持outerHTML属性。
(3).insertAdjacentHTML()方法:Chrome继承自Element.prototype,IE继承自HTMLELement.prototype。两个参数:插入位置和要插入的HTML文本。//作为前一个同辈元素插入 ele.insertAdjacentHTML(‘beforeBegin‘, "<p>Hello</p>"); //作为后一个同辈元素插入 ele.insertAdjacentHTML(‘afterEnd‘, "<p>Hello</p>"); //作为第一个子元素插入 ele.insertAdjacentHTML(‘afterBegin‘, "<p>Hello</p>"); //作为最后一个子元素插入 ele.insertAdjacentHTML(‘beforeBegin‘, "<p>Hello</p>");
兼容性:
for(var i=0;i<len;i++){ ul.innerHTML +=‘<li>‘+i+‘</li>‘; // 要避免这种频繁操作 }
这种每次循环都设置一次innerHTML做法很低效,而且每次循环还要从innerHTML中读取一次信息,意味着每次循环要访问两次innerHTML。最好做法是单独构建字符串后再一次性将结果字符串赋给innerHTML
var itemHtml =‘‘; for(var i=0;i<len;i++){ itemHtml += ‘<li>‘+i+‘</li>‘; } ul.innerHTML = itemHtml;
专有扩展
<meta http-equiv="X-UA-Compatible" content="IE=IEVersion">
这里IE的版本有以下不同的值而且这些值不一定与上述4种文档模式对应:
忽略文档类型声明的版本:
Edge:始终以最新文档模式渲染页面,对于IE8始终保持以IE8标准模式渲染页面,对于IE9始终保持以IE9标准模式渲染页面。
9:强制以IE9标准模式渲染页面。
8:强制以IE8标准模式渲染页面。
7:强制以IE7标准模式渲染页面。
5:强制将文档模式设置为IE5。
不忽略文档类型声明的版本:
EmulateIE9:如果有文档类型声明,以IE9标准模式渲染页面,否则将文档模式设置为IE5。
EmulateIE8:如果有文档类型声明,以IE8标准模式渲染页面,否则将文档模式设置为IE5。
EmulateIE7:如果有文档类型声明,以IE7标准模式渲染页面,否则将文档模式设置为IE5。
<!--想让文档模式像在IE7中一样 --> <meat http-equiv="X-UA-Compatible" content="IE=EmulateIE7"> <!--不打算考虑文档类型声明,直接使用IE7标准模式--> <meat http-equiv="X-UA-Compatible" content="IE=7">
没有规定说必须在页面中设置X-UA-Compatible,默认情况下浏览器会通过文档类型声明确定是使用最佳的可用文档模式还是混杂模式。通过document.documentMode属性可知给定页面使用的是什么文档模式,这个属性只在IE中有(继承自Document.prototype)它返回文档模式的版本号。
掩码 | 节点关系 |
1 | 无关(给定节点不在当前文档中) |
2 | 居前(给定节点在DOM树中位于参考节点之前) |
4 | 居后(给定节点在DOM树中位于参考节点之后) |
8 | 包含(给定节点是参考节点祖先) |
16 | 被包含(给定节点是参考节点后代) |
var result = document.documentElement.compareDocumentPosition(document.body); console.log(result);// 20 console.log(!!(result&16));// true
result结果并不是16而是20是因为居后4和被包含16之和。20化为二进制位10100,16化为二进制10000,按位与结果为10000(16),!!两个逻辑非操作会将该数值转化为布尔值。这里说一下为什么要和16按位与,我觉得是因为按位与是都是1&1才是1,其余是0,所以当节点关系不是包含关系而是其他关系(掩码总是<16)所以小于16的数与10000怎样与都是返回0的。
写一个通用的contains函数:
/* 判断某节点是否是另外一个节点的后代的兼容性代码 refNode:参照节点 targetNode:要检查的节点 */ function contains(refNode, targetNode){ if(typeof refNode.contains == ‘function‘){ return refNode.contains(targetNode); }else if(typeof refNode.compareDocumentPosition == ‘function‘){ return !!(refNode.compareDocumentPosition(targetNode)&16); }else{ var node = targetNode.parentNode; do{ if(node == refNode){ return true; }else{ node = node.parentNode; } }while(node!== null); return false; } }
ele.innerText = ele.innerText;
FF低版本不支持不支持innerText,但支持作用类似的textContent属性(继承自Node.prototype),textContent是DOM Level3规定的一个属性,其他支持textContent属性的浏览器还有IE9+和其他主流浏览器。
兼容性代码:
function getInnerText(ele){ return (typeof ele.textContent == ‘string‘)?ele.textContent:ele.innerText; } function setInnerText(ele, str){ if(typeof ele.textContent == ‘string‘){ ele.textContent = str; }else{ ele.innerText = str; } }
<=IE8的innerText会忽略行内样式和脚本。因此避免从包含行内样式或行内脚本的DOM子树副本或片段中读取文本。
(2).outerText属性:继承自HTMLElement.prototype,作用范围扩大到包含调用它的节点,写模式下它会替换整个元素(包括子节点)div.outerText ="xxx"; //等价于 var text = document.createTextNode(‘xxx‘); div.parentNode.replaceChild(text, div);
参考
《JavaScript高级程序设计》
标签:
原文地址:http://www.cnblogs.com/venoral/p/5459495.html