对DOM的两个主要扩展是Selectors API和HTML5。
选择符API
Selectors API致力于让浏览器原生支持CSS查询。在没有原生支持之前,只能通过javascript代码来完成查询操作。之后,解析和树查询操作可以在浏览器内部通过编译后的代码来完成,极大地改善了性能。
querySelector()方法
querySelector()
方法接收一个CSS选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null。
通过Document类型调用时,会在文档元素的范围内查找匹配的元素。而通过Element类型调用时,只会在该元素后代元素的范围内查找匹配的元素。
<ul>
<li class='one'></li>
<li></li>
<li></li>
</ul>
$ul = document.querySelector('ul');
$ul.querySelector('.one')
// return <li class='one'></li>
复制代码
querySelectorAll()方法
querySelectorAll()方法接收的参数与querySelector()方法一样,都是一个CSS选择符,但返回的时所有匹配的元素。这个方法返回的时一个Nodelist实例。
<ul>
<li class='one'></li>
<li></li>
<li></li>
</ul>
$ul = document.querySelector('ul');
$ul.querySelectorAll('.two')
// NodeList[]
复制代码
可以调用上面两种方法的node类型包括:document、element、DocumentFragment。
matchesSelector()方法
Element类型新增了一个方法matchesSelector()
。这个方法接收了一个参数,即CSS选择符,如果调用元素与该选择符匹配,返回true;否则,返回false。
这个方法还没有被所有浏览器都支持。
元素遍历
专门用于element类型的元素遍历
- childElementCount
- firstElementChild
- lastElementChild
- perviousElementSibling
- nextElementSibling
HTML5
HTML5规范则围绕如何使用新增标记定义了大量Javascript API。其中一些API和DOM重叠,定义了浏览器应该支持的DOM扩展。
与类相关的扩充
getElementsByClassName()方法
这个方法可以通过document对象以及所有HTML元素调用。
classList属性
在操作类名时,需要通过className属性添加、删除和替换类名。因为className中是一个字符串,所以即使只修改字符串的一部分,也必须每次都设置整个字符串的值。
<div class='a b c'></div>
var className = div.className.split(/\s+/);
var pos = -1;
var i;
var len;
for (i=0, len=className.length; i < len; i++) {
if (className[i] == 'a') {
pos = i;
break;
}
}
//删除类名
className.splice(i, 1)
//把剩下的类名拼成字符串并重新设置
div.className = className.join(' ');
复制代码
HTML5新增了一种操作类名的方式,可以让操作更简单也更安全,那就是为所有元素添加classList属性。这个classList属性是新集合类型DOMTokenList的实例。
DOMTokenList有一个表示自己包含多少元素的length属性,而要取得每个元素可以使用item()方法,也可以使用方括号语法。此外这个新类型还定义如下方法。
- add(value): 将给定的字符串值添加到列表中。如果值已经存在,就不添加了。
- contains(value): 表示列表中是否存在给定的值,如果存在则返回true,否则返回false。
- remove(value): 从列表中删除给定的字符串
- toggle(value): 如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。
焦点管理
document.activeElement属性,这个属性始终引用了DOM中当前获得了焦点的元素。元素获得焦点的方式有页面加载、用户输入和在代码中调用focus()方法。
var dom = document.getElementById('dom');
dom.focus();
document.activeElement === dom; // true
dom.hasFocus(); // true
复制代码
HTMLDocument变化
添加readyState属性
Document的readyState属性有两个可能的值:
- loading,正在加载文档
- complete,已经加载文档 document.readyState属性的基本用法如下:
if(document.readyState === 'complete') {
// 执行操作
}
复制代码
head属性
新增document.body
方法,直接引用元素
字符集属性
HTML5新增了几个与文档字符集有关的属性。其中,charset属性表示文档中实际使用的字符集,也可以用来指定新字符集。
自定义数据属性
HTML5规定可以为元素添加非标准的属性,但要添加前缀data-。
var dom = `<div id='myDiv' data-appid='234' data-myname='zc'></div>`;
var $container = document.createElement('div');
$container.innerHTML = dom;
var $dom = $container.firstElementChild;
$dom.dataset; // DOMStringMap {appid: "234", myname: "zc"}
$dom.dataset.appid = 12;
$dom.dataset; // DOMStringMap {appid: "12", myname: "zc"}
复制代码
插入标记
使用插入标记的技术,直接插入HTML字符串不仅更简单,速度也更快。以下与插入标记相关的DOM扩展已经纳入了HTML5规范。
innerHTML属性
在读模式下,innerHTML属性返回与调用元素的所有子节点(包括元素、注释和文本节点)对应的HTML标记。在写模式下,innerHTML会根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点。
但是,不同浏览器返回的文本格式会有所不同。IE和Opera会将所有标签转换成大写形式。而Safari、Chrome和Firefox则会原原本本地按照原先文档中的格式指定返回HTML,包括空格和缩进。
在写模式下,innerHTML的值会被解析为DOM子树,替换调用元素原来的所有子节点。因为它的值被认为是HTML,所以其中的所有标签都会按照浏览器处理HTML的标准方式转换为元素。
为innerHTML设置HTML字符串后,浏览器会将这个字符串解析为相应的DOM树。因此设置了innerHTML之后,再从中读取HTML字符串,会得到与设置时不一样的结果。原因在于返回的字符串是根据原始HTML字符串创建的DOM树经过序列化之后的结果。
outerHTML属性
在读模式下,outerHTML返回调用它的元素及所有子节点的HTML标签。在写模式下,outerHTML会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换调用元素。
insertAdjacentHTML()方法
插入标记的最后一个新增方式是insertAdjacentHTML()
方法。这个方法最早也是在IE中出现的,它接受两个参数:插入位置和要插入的HTML文本。第一个参数必须是下列值之一:
beforeBegin
,在当前元素之前插入一个紧邻的同辈元素
内存与性能问题
使用本节介绍的方法替换子节点可能会导致浏览器的内存占用问题,尤其在IE中,问题更加明显。在删除带有事件处理程序或引用了其他Javascript对象子树时,就有可能导致内存占用问题。假设某个元素有一个事件处理程序(或者引用了一个javascript对象作为属性),在使用前述某个属性将该元素从文档树中删除后,元素与事件处理程序(javascript对象)之间的绑定关系在内存中并没有一并删除。如果这种情况频繁出现,页面占用的内存数量就会明显增加。因此,在使用innerHTML、outerHTML、insertAdjacentHTML()方法时,最好先手工删除要被替换的元素的所有事件处理程序和Javascript对象属性。
同理: 每次循环都设置一次innerHTML的做法效率很低。
for (var i = 0; len = values.length; i < len; i++) {
ul.innerHTML += "<li>" + values[i] + "</li>"; // 要避免这种频繁操作
}
复制代码
效率更高的:
for (var i = 0, len = values.length; i <len; i++) {
itemsHtml += "<li>" + values[i] + "</li>";
}
ul.innerHTML = itemsHTML;
复制代码
那些没有标准化的专用扩展
文档模式
IE8引入了一个新的概念叫做“文档模式”(document mode)。页面的文档模式决定了可以使用什么功能。换句话说,文档模式决定了你可以使用哪个级别的CSS,可以在JavaScript中使用哪些API,以及如何对待文档类型(doctype)。到了IE9,总共有以下4种文档模式。
- IE5: 以混杂模式渲染页面。
- IE7: 以IE7标准模式渲染页面。IE8及更高版本中的新功能都无法使用。
- IE8: 以IE8标准模式渲染页面。IE8中的新功能都可以使用,因此可以使用Selectors API、更多CSS2选择符和某些CSS3功能,还有一些HTML5的功能。不过IE9中的新功能无法使用。
- IE9:以IE9标准模式渲染页面。IE9中的新功能都可以使用,比如ECMAScript5、完整的CSS3以及更多HTML5功能。这种文档模式是最高级的模式。
<meta http-equiv='X-UA-Compatible' content='IE=IEVersion'>
复制代码
The http-equiv attribute is used by servers to gather information about a page using the HTTP header. The meta tag’s http-equiv attribute set is similar to a http header.
children属性
这个属性是HTMLCollection的实例,只包含元素中同样还是元素的子节点。除此之外,children属性与childNodes没有什么区别。即在元素只包含元素子节点时,这两个属性的值相同。
contains方法
在实际开发中,经常需要知道某个节点是不是另一个节点的后代。IE为此率先引入了contains()方法,以便不通过在DOM文档树中查找即可获得这个信息。调用contains()方法的应该是祖先节点,也就是搜索开始的节点,这个方法接收一个参数,即要检测的后代节点。如果被检测的节点是后代节点,该方法返回true;否则,返回false。
document.documentElement.contains(document.body); // true
复制代码
使用DOM Level3 compareDocumentPosition()也能够确定节点间的关系。这个方法用于确定两个节点间的关系,返回一个表示关系的位掩码。
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;
}
node = node.parentNode;
} while (node !== null);
return false;
}
}
复制代码
插入文本
前面介绍过,IE原来专有的插入标记的属性innerHTML和outerHTML已经被HTML5纳入规范。但另外两个插入文本的专有属性则没有这么好的运气。这两个没有被HTML5看中的属性是innerText和outerText。
innerText属性
通过innerText属性可以操作元素中包含的所有文本内容,包括子文档树中的文本。在通过innerText读取值时,它会按照由浅入深的顺序,将子文档树中的所有文本拼接起来。在通过innerText写入值时,结果会删除元素的所有子节点,插入包含相应文本值的文本节点。
支持innerText属性的浏览器包括IE4+、safari 3+、Opera 8+和Chrome。Firefox虽然不支持innerText,但支持作用类似的textContent属性。textContent是DOM Level3规定的一个属性。为了确保跨浏览器兼容,有必要编写一个类似于下面的函数来检测可以使用哪个属性。
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;
}
}
复制代码
outerText属性
除了作用范围扩大到了包含调用它的节点之外,outerText与innerText基本上没有多大区别。
div.outerText = 'Hello World';
// 这行代码实际上相当于如下两行代码
var text = document.createTextNode('Hello World');
div.parentNode.replaceChild(text, div)
复制代码
滚动
HTML5之前的规范并没有就与页面滚动相关的API做出相关规定。但HTML5在将scrollIntoView()纳入规范之后,仍然还有其他几个专有方法可以在不同的浏览器中使用。下面列出的几个方法都是对HTMLElement类型的扩展,因此在所有元素中都可以调用。
小结
本章主要介绍了一些DOM扩展:
- Selectors API,定义了两个方法,让开发人员能够基于CSS选择符从DOM中取得元素,这两个方法是querySelector()和querySelectorAll()。
- Element Traversal,为DOM元素定义了额外的属性,让开发人员能够更方便地从从一个元素跳到另一个元素。之所以会出现这个扩展,是因为浏览器处理DOM元素间空白符的方式不一样。
- HTML5,为标准的DOM定义了很多扩展功能。
- 等等