(一)DOM扩展
对DOM的主要的扩展是SelectorsAPI(选择符API)和HTML5,还有一个Element Travesal规范。
1.选择符API
jQuery的核心就是通过CSS选择符查询DOM文档取得元素的引用,从而抛开了getElementById( ) 和 getElementsByTagName( ).
Selectors API Level 1的核心是两个方法:querySelector( )和 querySelectorAll( ).
- querySelector( ):接收一个css选择符,返回与该模式匹配的第一个元素。如果没有找到匹配的元素,则返回null。
<body> <ul> <li>First item</li> <li class="selected">Second item</li> <li>Third item</li> </ul> <script> if(document.querySelector){ var body = document.querySelector("body"); alert(body); //[object HTMLBodyElement] var selected = document.querySelector(".selected"); alert(selected.nodeName); //LI var img = document.body.querySelector("img.button"); alert(img); //null }else{ alert("Not Supported QuerySelector"); } </script> </body>
通过Document 类型调用querySelector( )方法时,会在文档元素的范围内查找匹配的元素。
而通过Element类型调用querySelector( )方法时,只会在该元素后代元素的范围内查找匹配的元素。
- querySelectorAll( )方法:接收参数也是一个CSS选择符,但返回的是所有匹配的元素而不仅仅是一个元素。返回一个NodeList对象。
能够调用querySelectorAll( )方法的类型包括Document、DocumentFragment 和 Element。
<body> <ul> <li>First item</li> <li class="selected">Second item</li> <li>Third item</li> </ul> <p><strong>Pellentesque habitant morbi tristique</strong> senectus et it amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> M auris placerat eleifend leo. <code>commodo vitae</code>, ornare c dui. <a href="#">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p> <blockquote><p>Lorem ipsum dolor sit amet, <strong>consectetur adipiscing elit</strong>. pretium ornare est.</p></blockquote> <script> if(document.querySelectorAll){ var em = document.querySelectorAll("em"); //alert(em.length); //1 var strongs = document.querySelectorAll("p strong"); alert(strongs.length);//2 }else{ alert("Not Supported QuerySelectorAll"); } </script> </body>
要取得返回的NodeList中的每一个元素,可以使用item()方法,也可以使用方括号语法。比如:
<script> if(document.querySelectorAll){ var strongs = document.querySelectorAll("p strong"); for(var i=0,len = strongs.length;i<len;i++){ strongs[i].className = "important"; } }else{ alert("Not Supported QuerySelectorAll"); } </script>
- matchesSelector( )方法:接收参数也是CSS选择符,如果调用元素与该元素符匹配,返回true;否则返回false。
如果要使用这个方法,做好编写一个包装函数。
<script> function matchesSelector(element,selector){ if(element.matchesSelector){ return element.matchesSelector(selector); }else if(element.msMatchesSelector){ //IE 9+ return element.msMatchesSelector(selector); }else if(element.mozMatchesSelector){ //firefox return element.mozMatchesSelector(selector); }else if(element.webkitMatchesSelector){ //Chrome return element.webkitMatchesSelector(selector); }else{ throw new Error("Not supported.") ; } } if(matchesSelector(document.body,"body.page1")){ alert("It's the page1!"); } </script>
2.Element Traversal 规范,元素遍历
对与元素间的空格,IE9及之前版本不会返回文本节点,而其他浏览器都会返回文本节点。这就导致了在使用childNodes 和 firstChild 等属性时的行为不一致。为了弥补这一差异,而同时又保持DOM规范不变,Element Traversal规范新定义了一组属性。
Element Traversal API 为DOM 元素添加了以下5个属性:
- childElementCount:返回子元素的个数(不包括文本节点和注释)。
- firstElementChild:指向第一个子元素;firstChild的元素版。
- lastElementChild:指向最后一个子元素;lastChild的元素版。
- previousElementSibling:指向前一个同辈元素;previousSibling的元素版。
- nextElementSibling:指向后一个同辈元素;nextSibling的元素版。
两种遍历,某元素的所有子元素的代码:
<body> <script> function travelElementChilds(){ var div = document.getElementById("myDiv"); var i, len=0, child = div.firstChild; alert(div.lastChild.nodeName); while(child != div.lastChild){ if(child.nodeType ==1){ len++; alert(child.nodeName+len); } child = child.nextSibling; } } function travelChilds(){ var i=0, len, div = document.getElementById("myDiv"), child=div.firstElementChild; len = div.childElementCount; //3 while(i<=len){ i++; alert(child.nodeName+i); child = child.nextElementSibling; } } </script> <div id="myDiv"> hello <li>item1</li> <li>item2</li> <li>item3</li> </div> <input type="button" value="travel elements" onclick="travelChilds()"> </body>
第二种是使用了Element Travelsal规范。
支持Element Travelsal规范的浏览器有IE9+、Firefox3.5+、Safari 4+、Chrome和Opera 10+
3.HTML5(本节只讨论与DOM节点相关的内容)
3.1 与类相关的扩充class属性
HTML5新增了很多API,致力于简化CSS类的用法。
- getElementsByClassName( )方法:
方法的参数:一个包含一或多个类名的字符串。传入多个类名时,类名的先后顺序不重要。
返回值:返回带有指定类的所有元素的NodeList。
作用:可以更方便地为带有某些类的元素添加时间处理程序,从而不必再局限于使用ID或标签名。
调用这个方法时,只有位于调用元素子树中的元素才会返回。在document对象上调用,始终会返回与类名匹配的所有元素;在Element元素上调用该方法就只会返回后代元素中匹配的元素。
function getClassNames(){ var allCurrent = document.getElementsByClassName("current"); var allUsername = document.getElementsByClassName("username"); var allCurrentUsername = document.getElementsByClassName("username current"); //class的先后顺序无所谓 alert("current.len = "+allCurrent.length); //3 alert("username.len ="+allUsername.length); //2 alert("current&username .len = "+allCurrentUsername.length);//1 } </script> <div id="myDiv"> <p class="current">hello</p> <li class="username">item1</li> <li class="current">item2</li> <li class="current username">item3</li> </div> <input type="button" value="travel elements" onclick="getClassNames()">
- classList 属性
在操作类名时,需要通过className属性添加、删除和替换类名。
下面的例子删除id=“li1”的三个类中的一个类:
function deleteClassNames(deleClass){ var li = documentElementById("li1"); var classNames = li.className.split(/s+/); var i,len=classNames.length; for(i=0;i<len;i++){ if(classNames[i] == deleClass) break; } classNames.splice(i,1); li.className = classNames.join(" "); }
使用HTML5新增的一种操作类名的方式,重写上面例子。
function html5ChangeStyle(){ var li = document.getElementById("li1"); li.classList.remove("bg"); }
直接使用classList属性,删除给定的类。
classList属性拥有下列方法:
add(value):将给定字符串值添加到列表中。如果值已经存在,就不添加了。
contains(value):列表中是否存在给定的值,若存在则返回true,否则返回false。
remove(value):从列表中删除给定的字符串。
toggle(value):如果列表中已经存在给定的值,则删除它,如果列表中没有给定的值,添加它。
function html5ChangeStyle(){ var li = document.getElementById("li1"); li.classList.toggle("bg"); }
有了classList 属性,除非你需要全部删除所有类名,或者重写元素的class属性,否则也就用不到className属性了。
支持classList 属性的浏览器有Firefox3.6+ 和 Chrome。
- 焦点管理
HTML5 也添加了辅助管理DOM 焦点的功能。
document.activeElement属性,这个属性始终会引用DOM中当前获得了焦点的元素。元素获得焦点的方式有页面加载、用户输入(通常是Tab键)和代码中调用focus()方法。
document.hasFocus()方法:这个方法用于确定文档是否获得了焦点。
window.onload=function(){ var button=document.getElementById("myButton"); button.focus(); alert(document.activeElement===button); //true alert(document.hasFocus()); //true }
3.3 HTMLDocument的变化
1)readyState属性:Document的readyState属性有两个可能的值:loading-正在加载文档、complete-已加载完文档。
document.readyState 实现一个文档已经加载完成的指示器。要实现这样一个指示器,必须借助onload 事件处理程序设置一个标签,表明文档已经加载完毕。
window.onload = function(){ if(document.readyState=="complete"){ alert("OK"); } }
2)兼容模式:浏览器渲染模式
自从IE6 开始区分渲染页面的模式是 标准的还是 混杂 的,检测页面的兼容模式就成为浏览器的必要功能。IE给document 添加了一个名为compatMode 的属性,这个属性告诉开发人员浏览器采用哪种渲染模式。
标准模式下,document.compatMode == "CSS1Compat";
混杂模式下,document.compatMode == "BackCompat";
window.onload = function(){ if(document.readyState=="complete"){ alert("OK"); }else if(document.readyState=="loading"){ alert("loading"); } if(document.compatMode=="CSS1Compat"){ alert("Standard"); }else if(document.compatMode=="BackCompat"){ alert("BackCompat"); } }
3)head属性
HTML5 新增了 document.head 属性,引用文档的<head>元素。
要引用文档的<head>元素,可以结合使用这个属性和另一种后备方法:
var head = document.head || document.getElementsByTagName("head")[0];
如果可用,就用document.head,否则仍然使用getElementsByTagName( )方法.
3.4 字符集属性
HTML5 新增了几个与文档字符集有关的属性。
1)charset 属性:表示文档中实际使用的字符集
可以通过<meta>元素、响应头部或直接设置charset属性修改这个值。
- 通过<meta>元素设置
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- document.charset 属性设置
document.characterSet ="GBK"; alert(document.characterSet); //gb2312
- defaultCharset :默认浏览器及操作系统的设置,当前文档默认的字符集
charset和defaultCharset 属性的值可能会不一样。
3.5 自定义数据属性
HTML5规定可以为元素添加非标准的属性,但要添加前缀data-,目的是为与元素提供与渲染无关的信息,或者提供语义信息。
<script> function getDIY(){ var div = document.getElementById("myDiv"); var appId = div.dataset.appid; var myName = div.dataset.myname; alert("appId:"+appId+";myName:"+myName); } </script> <div id="myDiv" data-appid="123456" data-myname="Nicolas"></div> <input id="myButton" type="button" value="elements" onclick="getDIY()">
貌似,属性名只能用小写字母表示,不然会undefined。
3.6 插入标记
1)innerHTML属性
- 在读模式下,innerHTML属性返回调用元素的所有子节点对应的HTML标记。
<script> function getInnerHtml(){ var div = document.getElementById("content"); alert(div.innerHTML); } </script> <div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item1</li> <li>Item2</li> </ul> </div> <input id="myButton" type="button" value="elements" onclick="getInnerHtml()">
返回结果:
- 在写模式下,innerHTML会根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点。
function setInnerHtml(){ var div = document.getElementById("content"); div.innerHTML = "Hello world!"; }
浏览器会立即更新,content中的内容变成了“Hello world!”。
无论什么时候,只要使用innerHTML从外部插入HTML,都应该首先以可靠的方式处理HTML。IE8为此提供了window.toStaticHTML()方法,这个方法接收一个参数,即一个HTML字符串;返回一个进过无害处理后的版本--从源HTML中删除所有脚本节点和事件处理程序属性。
var text = "<a href="#" onclick="alert('hi')">Click Me</a>"; var sanitized = window.toStaticHTML(text); alert(sanitized);
结果显示如下:
2)outerHTML属性
- 在读模式下,outerHTML返回调用它的元素及所有子节点的HTML标签。
<script> function getOuterHtml(){ var div = document.getElementById("content"); alert(div.outerHTML); } </script> <div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item1</li> <li>Item2</li> </ul> </div> <input id="myButton" type="button" value="elements" onclick="getOuterHtml()">
结果如下:注意,div也会一起返回。
- 在写模式下,outerHTML会根据指定的HTML字符串创建新的DOM子树。
<script> function setOuterHtml(){ var div = document.getElementById("content"); div.outerHTML = "<p>This is a paragraph.</p>"; } </script> <div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item1</li> <li>Item2</li> </ul> </div> <input id="myButton" type="button" value="elements" onclick="setOuterHtml()">
结果如下:新创建的<p>元素会取代DOM树中的<div>元素。
用下面的操作可以完成相同的操作:
<script> function equalOuterHTML(){ var div = document.getElementById("content"); var p = document.createElement("p"); p.appendChild(document.createTextNode("This is a paragraph.")); div.parentNode.replaceChild(p,div); //newChild,oldChild } </script> <div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item1</li> <li>Item2</li> </ul> </div> <input id="myButton" type="button" value="elements" onclick="equalOuterHTML()">
3)insertAdjacentHTML(“插入位置”,“要插入的HTML文本”) 方法
插入标记的最后一个新增方式是insertAdjacentHTML()方法。
第一个参数的值必须是下列值之一:
1:“beforebegin”,在当前元素之前插入一个紧邻的同辈元素;
2:“afterbegin”,在当前元素之下插入一个新的子元素或在第一个子元素之前插入新的子元素;
3:“beforeend”,在当前元素之下插入一个新的子元素或在最后一子元素之后再插入新的子元素;
4:“afterend”,在当前元素之后插入一个紧邻的同辈元素。
第二个参数是一个HTML字符串(与innerHTML和outerHTML的值相同),如果浏览器无法解析该字符串,就会抛出错误。
function userInsertAjacent(){ var div= document.getElementById("content"); var str = "<p>Hello world!</p>"; div.insertAdjacentHTML("beforeBegin",str); //作为前一个同辈元素插入 div.insertAdjacentHTML("afterBegin",str); //作为第一个子元素插入 div.insertAdjacentHTML("beforeEnd",str); //作为最后一个子元素插入 div.insertAdjacentHTML("afterEnd",str); //作为后一个同辈元素插入 }
3.7 scrollIntoView()方法
如何滚动页面也是DOM规范没有解决的一个问题。HTML5最终选择了scrollIntoView()作为标准方法。
scrollIntoView( true/false) 可以在所有HTML元素上调用,通过滚动浏览器窗口或者某个容器元素,调用元素就可以出现在视图中。
用途:当页面发生变化时,一般会用这个方法来吸引用户的注意力。当然,为某个元素设置焦点也会导致浏览器滚动并显示出获得焦点的元素。
4.专有模式
仍有大量专有的DOM扩展没有成为标准。
4.1 文档模式:document.documentMode属性
“文档模式”决定了你可以使用哪个级别的CSS,可以在Javascript中使用哪些API,以及如何对待文档类型(doctype).
通过document.documentMode属性可以知道给定页面使用的是什么文档模式。知道页面采用什么文档模式,有助于理解页面的行为方式。无论在什么模式下,都可以访问这个属性。
要强制设置浏览器以某种方式渲染页面,可以使用HTTP头部信息X-UA-Compatible,或者通过等价的<meta>标签来设置。
<meta http-equiv="X-UA-Compatible" content="IE=IEVersion">
4.2 children属性
IE9之前的版本与其他浏览器在处理文本节点中的空白符时有差异,就因此出现了children属性,这个属性是HTMLCollection的实例,只包含元素中同样是元素的子节点。
children属性 与childNodes的区别:只有在元素中只包含元素子节点时,这两个属性的值相同。
<script> function getChildrensLen(){ var div = document.getElementById("content"); alert(div.childNodes.length); //5 alert(div.children.length);//2 alert(div.children[0].nodeName+";"+div.children[1].nodeName);//P;UL } </script> <div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item1</li> <li>Item2</li> </ul> </div> <input id="myButton" type="button" value="elements" onclick="getChildrensLen()">
4.3 contains()方法(未完待续)
在实际开发过程中,经常需要知道某个节点是不是另一个节点的后代。IE为此率先引入了contains(),这样就可以不通过在DOM文档树中查找即可获得这个信息。
4.4 插入文本
innerHTML和outHTML已经被HTML5纳入规范。两个插入文本的专有属性没有被HTML5看中:innerText 和outText
1)innerText属性
通过innerText属性可以操作元素中包含的所有文本内容,包括子文档树中的文本。
- 通过innerText读取值时,它会按照由浅入深的顺序,将子文档树中的所有文本拼接起来。
<script> function getInnerText(){ var div = document.getElementById("content"); alert(div.innerText); } </script> <div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item1</li> <li>Item2</li> </ul> </div>
结果显示:
- 通过innerText写入值时,结果会删除元素的所有子节点,插入包含相应文本值的文本节点。
div.innerText="<p>Hello new Para</p>";
FireFox不支持。
为了确保跨浏览器兼容,我们使用下面的函数来检测使用哪个属性:
<body> <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> <input type="button" value="Replace InnerText Simple" onclick="replaceTextSimple()"> <input type="button" value="Replace InnerText Complex" onclick="replaceTextComplex()"> <script type="text/javascript"> function getInnerText(element){ return (typeof element.textContent == "string") ? element.textContent : element.innerText; } function setInnerText(element, text){ if (typeof element.textContent == "string"){ //Firefox element.textContent = text; } else { element.innerText = text; } } function replaceTextSimple(){ var div = document.getElementById("content"); setInnerText(div, "Hello world!"); alert(getInnerText(div)); } function replaceTextComplex(){ var div = document.getElementById("content"); setInnerText(div, "Hello & welcome, <b>"reader"!</b>"); alert(getInnerText(div)); } </script> </body>
2)outerText属性
- 在读取文本时,outerText 与 innerText 的结果完全一样。
- 在写模式下,outerText 就完全不同了:outerText 不只是替换调用它的元素的子节点,而是会替换整个元素(包括子节点)
这个属性不常用,也不建议使用,因为元素会从文档中被删除,无法访问。