DOM Extensions(DOM扩展)
文章目录
DOM Extensions(DOM扩展)
一、SELECTORS API 新增选择器API
二、ELEMENT TRAVERSAL 元素遍历
三、Html5
1. class类相关的扩充
getElementsByClassName
className属性、 classList属性
2. Focus Management 焦点管理
3. Changes to HTMLDocument(针对document对象新增3个属性)
4. 针对字符新增 charset属性
5. 自定义data属性
6. Markup Insertion插入标记
innerHTML与outerHTML
insertAdjacentHTML
内存和性能问题
7. scrollIntoView()
四、专有扩展(PROPRIETARY EXTENSIONS)
文档模式(Document Mode)
children属性
`contains()`方法
插入标记( Markup Insertion)
滚动( Scrolling)
五、总结(summary)
一、SELECTORS API 新增选择器API
Selectors API(www.w3.org/TR/selectors-api),是由W3C发起的,用于支持浏览器中的CSS查询,
其中 Selectors API Level 1的核心部分定义了两个方法querySelector()、 querySelectorAll(),兼容ie8+ 用在document、Element类型元素上
Selectors API Level 2 规范中定义了 matchesSelector()方法,只能用在Element类型的元素上,判断给定的class或者id是否是当前元素上的属性,兼容ie9+,并且各个浏览器兼容的前缀都不一样,见代码中的对该功能的封装
//get the body element var body = document.querySelector("body"); //get the element with the ID "myDiv" var myDiv = document.querySelector("#myDiv"); //get first element with a class of "selected" var selected = document.querySelector(".selected"); //get first image with class of "button" var img = document.body.querySelector("img.button"); //get all <em> elements in a <div> (similar to getElementsByTagName("em")) var ems = document.getElementById("myDiv").querySelectorAll("em"); //get all elements with class of "selected" var selecteds = document.querySelectorAll(".selected"); //get all <strong> elements inside of <p> elements var strongs = document.querySelectorAll("p strong"); // 对matchesSelector功能兼容各个浏览器的封装 function matchesSelector(element, selector) { if (element.matchesSelector) { return element.matchesSelector(selector); } else if (element.msMatchesSelector) { return element.msMatchesSelector(selector); } else if (element.mozMatchesSelector) { return element.mozMatchesSelector(selector); } else if (element.webkitMatchesSelector) { return element.webkitMatchesSelector(selector); } else { throw new Error("Not supported."); } } if (matchesSelector(document.body, "body.page1")) { // 如果body元素上class属性对应的值中存在page1的class类名,返回true //do something }
二、ELEMENT TRAVERSAL 元素遍历
思考如下代码:
/*<ul id="mylist"> <li>Item 1</li> </ul> */ var mylist = document.getElementById('mylist'); alert(mylist.childNodes.length); // 3 > ie9+ 其他浏览器 alert(mylist.childNodes.length); // 1 > ie8
在ie8中,空白节点不会被当成text node返回,而高版本浏览器则会返回空白文本节点,这就导致了使用 childNodes、firstChild时浏览器表现不一致。
为了解决这个问题,而又同时保持现有的DOM规范, Element Traversal( www.w3.org/TR/ElementTraversal/)定义了一组新的属性,这5个属性如下:
childElementCount、 firstElementChild、 lastElementChild 、 previousElementSibling、 nextElementSibling
这五个方法都是在 Element元素类型上的操作,所以就不用担心 上述空白文本节点(Text类型)导致的问题啦。
兼容性: Internet Explorer 9+, Firefox 3.5+, Safari 4+, Chrome, andOpera 10+.
三、Html5
1. class类相关的扩充
自从HTML4依赖,class属性被用的越来越多,使得js与css的class类交互增加,包括动态的改变class、给定class查询document,为了适应开发人员,Html5规范加入了一些方法,让css的class类使用更便捷。
getElementsByClassName
兼容 Internet Explorer 9+, Firefox 3+,Safari 3.1+, Chrome, and Opera 9.5+
//get all elements with a class containing “username” and “current”, though it //doesn’t matter if one is declared before the other var allCurrentUsernames = document.getElementsByClassName("username current"); //get all elements with a class of “selected” that exist in myDiv’s subtree var selected = document.getElementById(“myDiv”).getElementsByClassName("selected");
className属性、 classList属性
使用
className删除元素的一个属性:(兼容所有浏览器)
/* <div class=”bd user disabled”>...</div> */ //remove the “user” class //first, get list of class names var classNames = div.className.split(/\s+/); //find the class name to remove var pos = -1, i, len; for (i = 0, len = classNames.length; i < len; i++) { if (classNames[i] == "user") { pos = i; break; } } //remove the class name classNames.splice(i, 1); //set back the class name div.className = classNames.join("");
使用 classList操作元素的class属性值 只兼容到 ie10+
//remove the "disabled" class div.classList.remove("disabled"); //add the "current" class div.classList.add("current"); //toggle the "user" class div.classList.toggle("user"); //figure out what’s on the element now if (div.classList.contains("bd") && !div.classList.contains("disabled")) { //do something ) //iterate over the class names for (var i = 0, len = div.classList.length; i < len; i++) { doSomething(div.classList[i]); }
2. Focus Management 焦点管理
H5新增的两个焦点管理属性:
document.activeElement - 当前获得了焦点元素的指针
document.hasFocus() - 是否当前document存在焦点
var button = document.getElementById(“myButton”); button.focus(); alert(document.activeElement === button); //true var button = document.getElementById(“myButton”); button.focus(); alert(document.hasFocus()); //true
支持浏览器 Internet Explorer 4+, Firefox 3+, Safari 4+, Chrome, andOpera 8+
csx.
activeElement获取当前获取焦点元素、hasFocus()判断当前文档是否有元素获取焦点(eg,是否有input被获取焦点)
document.activeElement默认为document.body,在文档未完全加载前,值为null
用户输入input的焦点获取通常通过tab键或者focus()方法
3. Changes to HTMLDocument(针对document对象新增3个属性)
HtmlDocument继承Document接口, window的全局对象document对象是 HtmlDocument类型的实例,以下是HTML5针对ocument对象扩展3属性,而这些属性可能在某些浏览器中早就已经作为专有扩展而存在了,这一次只是将其正式加入HTML5规范(以便所有浏览器对照规范去实现)
document.readyState
文档状态 loading、 complete
兼容ie4+
document.compatMode
浏览器模式
CSS1Compat表示标准模式
BackCompat表示怪异模式(quirks mode)
兼容ie6+
document.head
指向head元素的指针
只有 Chrome and Safari 5实现了该属性
eg:
if (document.readyState == “complete”){ //do something } if (document.compatMode == “CSS1Compat”){ alert(“Standards mode”); } else { alert(“Quirks mode”); } var head = document.head || document.getElementsByTagName(“head”)[0];
csx.
document对象新增的3个属性:readyState、compatMode、head
4. 针对字符新增 charset属性
document.charset 获取或者设置文档编码
文档编码通常在标签中被指定,也可以通过document.charset属性设置
eg:
兼容所有浏览器
csx. document.charset属性,获取、设置文档编码
5. 自定义data属性
element.dataset 获取、设置元素上data-属性的值,ie11开始完全兼容该属性
//<div id="myDiv" data-csx='001'>xxx</div> var myDiv = document.getElementById("myDiv"); alert(myDiv.dataset.csx); // 获取属性 001 myDiv.dataset.csx = '002'; // 设置属性 alert(myDiv.dataset.csx); // 002
6. Markup Insertion插入标记
插入html元素到document,以前做法是新增一些DOM节点,然后将这些节点按顺序插入DOM树,如下,但是,如果要插入大量的html元素,这样操作就显得冗余笨重,为此,HTML5标准化了下列3个方法(innerHTML、outerHTML、insertAdjacentHTML),让元素插入更简单快速。
/*<ul id="myList"> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul>*/ var myList = document.getElementById("myList"); var newLi = document.createElement("li"); newLi.appendChild(document.createTextNode("new item")); myList.appendChild(newLi);
innerHTML与outerHTML
innerHTML与outerHTML
- 读取模式下 outerHtml比innerHtml多一层最外面的元素,如下代码
- 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> */ var div = document.getElementById('content'); console.log("innerHtml-->",div.innerHTML); console.log("outerHtml-->",div.outerHTML); /* innerHtml--> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul id="myList"> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> outerHtml--> <div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul id="myList"> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div> */
insertAdjacentHTML
insertAdjacentHTML 在元素不同的位置插入html元素
兼容ie8+
//insert as previous sibling element.insertAdjacentHTML("beforebegin", "<p>Hello world!</p>"); //insert as first child element.insertAdjacentHTML("afterbegin", "<p>Hello world!</p>"); //insert as last child element.insertAdjacentHTML("beforeend", "<p>Hello world!</p>"); //insert as next sibling element.insertAdjacentHTML("afterend", "<p>Hello world!</p>");
内存和性能问题
a. 内存问题:
在用上述3个方法替换子节点时,如果该子节点上绑定了事件、js对象,这些子节点被移除时,绑定的事件处理、js对象仍在内存中,增加了页面占用的内存,所以在使用innerHtml、outerHtml、insertAdjacentHtml时,最好手工移除元素上的事件处理程序和js对象属性。ie8中增加了该方法window.toStaticHTML()来移除事件,其他浏览器、ie其他版本需要手工移除
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>"
b. 性能问题:
为什么在大量插入html元素时,innerHtml(outerHtml),比那些大量的DOM操作节点的方式要更高效呢?
因为在每一个调用 innerHtml(outerHtml)插入元素时,会创建一个解析器程序去解析要插入的字符串,解析成dom节点插入文档,而该解析器程序是浏览器级别的代码,通常用C++写的,所以比js快
另一方面,这也导致了一些性能开销,所以要限制调用innerHtml/outerHtml的次数
for (var i=0, len=values.length; i < len; i++){ ul.innerHTML += "<li>" + values[i] + "</li>"; //avoid!! } // 避免上面的写法,正确写法如下 var itemsHtml = ""; for (var i=0, len=values.length; i < len; i++){ itemsHtml += "<li>" + values[i] + "</li>"; } ul.innerHTML = itemsHtml;
7. scrollIntoView()
在各大浏览器的专有方法中,挑选出了 scrollIntoView() 方法加入HTML5规范
scrollIntoView(),将指定元素滚动到视口区域,加了参数true,表示元素的顶部和viewport顶部对齐,另外 focus()获取焦点的方法,也有同样的效果
兼容所有浏览器
//make sure this element is visible document.forms[0].scrollIntoView(); // focus也有同样效果 document.forms[0].focus();
四、专有扩展(PROPRIETARY EXTENSIONS)
前面介绍了,各个浏览器专有的DOM扩展是为了丰富自己浏览器的功能,当这些功能被推广开来,其他浏览器也去实现该功能,这些扩展也就成了事实上的标准,虽然他们还未被进入DOM规范,本节介绍的就是这类专有扩展。
文档模式(Document Mode)
IE8引进了文档模式的概念,一句话,就是在不同文档模式下,对CSS、JS的支持程度是不同的。
<meta http-equiv=”X-UA-Compatible” content=”IE=IEVersion”>
可以通过在meta标签中 设置IEVersion字段来改变文档模式
eg:
强制将文档模式切换为ie9标准模式
children属性
跟 Element Traversal API定义的 childElementCount、firstElementChild、 lastElementChild、 previousElementSibling、 nextElementSibling定义的五个属性一样,都是为了解决浏览器将空白字符解析成文本节点的问题,children属性返回当前元素的所有的子节点(only Element类型)
兼容性: Internet Explorer 5(除了ie8版本仍然返回文本节点、注释节点), Firefox 3.5, Safari 2 (buggy), Safari 3(complete), Opera 8, and Chrome (all versions).
/* <ul id="mylist"> <li>Item 1</li> </ul> */ var mylist = document.getElementById("mylist"); console.log(mylist.children.length); // 1 不包含空白字符的文本节点了 console.log(mylist.children[0]);
contains()方法
nodeA.containers(nodeB) 给定的nodeB节点是否包含在nodeA节点中,兼容 Internet Explorer,Firefox 9+, Safari, Opera, and Chrome
另外一个方法 compareDocumentPosition()( DOM Level 3中的),跟contains功能一样,兼容: Internet Explorer 9+, Firefox,Safari, Opera 9.5+, and Chrome
有个工具代码结合了上述两个方法,保证各个浏览器都能实现判断一个节点是否包含另外一个节点的功能,如下代码:
// contains 判断html元素是否包含body元素 alert(document.documentElement.contains(document.body)); //true, // compareDocumentPosition 返回值是16位掩码 var result =document.documentElement.compareDocumentPosition(document.body); alert(!!(result & 16)); // true // 兼容各个浏览器的封装代码(主要对Safari低版本做了兼容) function contains(refNode, otherNode) { if (typeof refNode.contains == "function" && (!client.engine.webkit || client.engine.webkit >= 522)) { return refNode.contains(otherNode); } else if (typeof refNode.compareDocumentPosition == "function") { return !!(refNode.compareDocumentPosition(otherNode) & 16); } else { var node = otherNode.parentNode; do { if (node === refNode) { return true; } else { node = node.parentNode; } } while (node !== null); return false; } }
插入标记( Markup Insertion)
innerHtml、outerHtml被加入了HTML5规范,另外还有两个实用的专有属性innerText、outerText并未加入H5规范
innerText 用于返回当前元素及其所有子节点上的文本,用于赋值时,会用新的文本节点替换原来元素上所有的子节点,兼容: Internet Explorer 4+, Safari 3+, Opera 8+, and Chrome
另外, DOM Level 3中定义的 textContent属性功能与innerText一样,兼容 Internet Explorer9+, Safari 3+, Opera 10+, and Chrome,firefox,可以写一个兼容性封装函数,如下代码区
outerText 用法同上,只是outText在获取、赋值时,会把当前元素上的内容一起返回、替换,并不常用
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; } }
如何利用 innerText移除指定元素的子元素的所有标签:
/* <div id="div"><span>hello</span><em>world!</em></div> */ var div = document.getElementById("div"); div.innerText = div.innerText; // 这一行代码足矣 console.log(div); // < div id="div">helloworld!</div>
滚动( Scrolling)
scrollIntoView()方法被加入了HTML5规范,所有浏览器都支持该方法,其他各个浏览器中,还有3个滚动的方法,scrollIntoViewIfNeeded(alignCenter)、scrollByLines(lineCount)、 scrollByPages(pageCount)
目前只有chrome、safari实现了这三个方法,我自己测试下来,chrome也不支持,如果支持的话,scrollByPages(-1)向下滚动一屏,这个方法会很有用。
//scroll body by five lines document.body.scrollByLines(5); //make sure this element is visible only if it’s not already document.images[0].scrollIntoViewIfNeeded(); //scroll the body back up one page document.body.scrollByPages(-1);
五、总结(summary)
DOM规定了与HTML文档交互的核心API,其他一些规范提供了对标准DOM的扩展,其中很多扩展都是基于各个浏览器专有扩展而发展来的,本文主要涉及了下面三类扩展规范
Selectors API:基于css选择器定义了两个查找DOM元素的方法: querySelector() querySelectorAll()
Element Traversal:遍历元素的5个方法,能够查找指定元素的各个位置上(元素前后、元素子节点的前后)的元素,这5个方法主要是为了解决childNodes、firstChild
获取元素的子节点时,会将空白字符解析成文本节点这一个问题
HTML5:html5提供了大量的对标准DOM的扩展,包括已经成为各个浏览器事实上的标准的方法(如innerHtml)和新增的方法,这些方法用来处理焦点管理、文档加载状态、字符集、自定义元素属性、标记批量插入、滚动等
其他浏览器专有属性。这些专有属性作为各个浏览器的专有属性,有些已经成为事实上的标准,日后可能会纳入规范中。
最后,作为开发者,为什么要知道这些DOM扩展呢?-- DOM扩展,扩展了DOM操作的属性和方法,我们可以用这些扩展的属性方法更高效巧妙的实现业务场景下的实际需求。