DOM的扩展
尽管DOM作为API已经非常完善了,但为了实现更多的功能,仍然会有一些标准或专有的扩展。2008年之前,浏览器几乎所有的DOM扩展都是专有的。此后,W3C着手将一已经成为事实标准的专有扩展标准化并写入规范当中。
对DOM的两个主要扩展是Selectors API(选择符API)和HTML5.这两个扩展都源自开发社区,而将某些常见做法及API标准化一直是众望所归。此外,还有一个不那么引人瞩目的Element Traversal(元素遍历)规范,为DOM添加了一些属性。虽然前述两个主要规范(特别是HTML5)已经涵盖了大量的DOM扩展,但专有扩展依然存在。
选择符API
Selectors API(www.w3.org/TR/selectors-api)是由W3C发起制定的一个标准,致力于让浏览器原生支持CSS查询。
Selectors API Level 1 的核心是两个方法:querySelector()
和querySelectorAll()
。在兼容的浏览器中,可以通过Document及Element类型的实例调用它们。目前已经完全支持Selectors API Level 1的浏览器器有IE8+、Firefox3.5+、Chrome和Opera10+。
querySelector()
querySelector()
接收一个CSS选择符,返回与该模式匹配的第一个元素,如果没有找到则返回null。
js
// 取得body元素
var body = document.querySelector('body');
// 取得ID为'myDiv'的元素
var myDiv = document.querySelector('#myDiv');
// 取得类为'selected'的第一个元素
var selected = document.querySelector('.selected');
// 取得类为'button'的第一个图像元素
var img = document.body.querySelector('img.button');
通过Document类型调用querySelector()
时,会在文档元素的范围内查找匹配的元素;而通过Element类型调用时,会在该元素的后代元素范围查找。
CSS选择符可以简单也可以复杂,视情况而定。如果传入了不被支持的选择符,会抛出错误。
querySelectorAll()
querySelectorAll()
同样接收一个CSS选择符作为参数,但返回的是所有匹配的元素而不仅仅是一个元素。这个方法返回的是一个NodeList的实例。
具体来说,返回的值实际上是带有所有属性和方法的NodeList,而其底层实现则类似于一组元素的快照,而非不断对文档进行搜索的动态查询。
只要传给querySelectorAll()
方法的CSS选择符有效,该方法都会返回一个NodeList对象,而不管找到多少匹配的元素。如果没找到匹配的元素,NodeList就是空的。
js
// 取得某div中所有em元素(类似于getElementsByTagName('em'))
var ems = document.getElementById('myDiv').querySelectorAll('em');
// 取得类为'selected'的所有元素
var selecteds = document.querySelectorAll('.selected');
// 取得所有p元素中的所有strong元素
var strongs = document.querySelectorAll('p strong');
要取得返回的NodeList中的每一个元素,可以使用item()方法,也可以使用方括号方法。
js
var i, len, strong;
for (i = 0, len = strongs.length; i < len; i++) {
strong = strongs[i]; // 或者strongs.item(i)
strong.className = 'important';
}
matchesSelector()
Selectors API Level 2 规范为Element类型新增了一个方法matchesSelector()
。这个方法接收一个参数,即CSS2选择符,如果调用元素与该选符匹配,返回true,否则返回false.
js
if (document.body.matchesSelector('body.page1')) {
// true
}
在取得某个元素引用的情况下,使用这个方法能够方便地检测它是否被querySelector()
或querySelectorAll()
方法返回。
使用这个方法最好是编写一个包装函数。
js
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 {
throw new Error('Not supported.');
}
}
if (matchesSelector(document.body, 'body.page1')) {
// 执行操作
}
元素遍历
对于元素间的空格,IE9及之前版本不会返回文本节点,而其他所有浏览器都会返回文本节点。这样,就导致了在使用childNodes
和firstChild
等属性时的行为不一致。为了弥补这一差异,而同时又保持DOM规范不变,Element Traversal规范(www.w3.org/TR/ElementTraversal/)新定义了一组属性。
Elemen Traversal API为DOM元素添加了以下5个属性。
- childElementCount:返回子元素(不包括文本节点和注释)的个数。
- firstElementChild:指向第一个子元素,firstChild的元素版。
- lastElementChild。
- previousElementSibling:指向前一个同辈元素,previousSibling的元素版。
- nextElementSibling。
使用这些新增的属性,跨浏览器遍历某元素的所有子元素,代码会更简洁。
js
var child = element.firstElementChild,
i,
len;
while (child !== element.lastElementChild) {
processChild(child); // 已知其是元素
child = child.nextElementSibling;
}
支持Element Traversal规范的浏览器有IE9+、Firefox3.5+、Safari4+、Chorme和Opera10+。
HTML5
对于传统HTML而言,HTML5是一个叛逆。所有之前的版本对JavaScript接口的描述都不过三言两语,主要篇幅都用于定义标记,与JavaScript相关的内容一概交给DOM规范去定义。
而HTML5规范则围绕如何使用新增标记定义了大量JavaScript API。其中一些API与DOM重叠,定义了浏览器应该支持的DOM扩展。
与类相关的扩充
getElementsByClassName()
HTML5添加的getElementsByClassName()
方法是最受人欢迎的一个方法,可以通过document对象
及所有HTML元素调用这个方法。
getElementsByClassName()
接收一个参数,即一个包含一个或多个类名的字符串,返回带有指定类的所有元素的NodeList。传入多个类名的时候,类名先后顺序不重要。
js
// 取得所有类中包含'username'和'current'的元素
var allCurrentUsernames = document.getElementsByClassName('username current');
// 取得ID为'myDiv'的元素中带有类名'selected'的所有元素
var selected = document.getElementById('myDiv').getElementsByClassName('selected');
支持getElementsByClassName()
方法的浏览器有IE9+、Firefox3+、Safari3.1+、Chorme和Opera9.5+。
classList属性
HTML5新增了一种操作类名的方式,可以让操作更简单也更安全,那就是为所有元素添加classList属性
。这个classList属性
是新集合类型DOMTokenList
的实例。与其他DOM集合类型类似,DOMTokenList
有一个表示自己包含多少个元素的length属性
,而要取得每个元素可以使用item()
方法,也可以使用方括号语法。此外,这个新类型还定义了如下方法:
- add(value):将给定的字符串值添加到列表中,如果值存在,就不添加了。
- contains(value):表示列表上中否存在给定的值,存在返回true,否则返回false。
- remove(value):从列表中删除给定的字符串。
- toggle(value):如果列表中已经存在给定的值,则删除,否则添加
支持classList属性
的浏览器有Firefox3.6+和Chrome。
js
// 删除'disabled'类
div.classList.remove('disabled');
// 添加'current'类
div.classList.add('current');
// 确定元素中是否包含既定的类名
if (div.classList.contains('bd') && !div.classList.contains('disabled')) {
// ...
}
// 迭代类名
for (var i = 0, len = div.classList.length; i < len; i++) {
doSomething(div.classList[i]);
}
焦点管理
activeElement属性
HTML5也添加了辅助管理DOM焦点的功能。首先就是document.activeElement属性
,这个属性始终会引用DOM中当前获得了焦点的元素。元素获得焦点的方式有页面加载、用户输入和在代码中调用focus()方法。
js
var button = document.getElementById('myButton');
button.focus();
alert(document.activeElement === button); // true
默认情况下,文档刚刚加载完,document.activeElement
中保存的是document.body元素
的引用。文档加载期间,document.activeElement
的值为null。
hasFocus()
hasFocus()
方法用于确定文档是否获得了焦点。
js
var button = document.getElementById('myButton');
button.focus();
alert(document.hasFocus()); // true
支持这两个属性的浏览器有IE4+、Firefox3+、Safari4+、Chorme和Opera8+。
HTMLDocument的变化
HTML5扩展了HTMLDocument,增加了新的功能。与HTML5中新增的其他DOM扩展类似,这些变化同样基于那些已经得到很多浏览器完美支持的专有扩展。
readyState属性
Document的readyState属性有两个可能的值:
- loading,正在加载文档;
- complete,已经加载完文档
js
if (document.readyState === 'complete') {
// 文档已经加载完毕
}
支持readyState属性
的浏览器有IE4+、Firefox3.6+、Safari+、Chorme和Opera9+。
兼容模式
document.compatMode属性
是为了告诉开发人员浏览器采用了哪种渲染模式。在标准模式下,这个属性的值等于CSS1Compat
,而在混杂模式下,值等于BackCompat
。
js
if (document.compatMode === 'CSS1Compat') {
alert('正在使用标准模式');
} else {
alert('正在使用混杂模式');
}
head属性
js
var head = document.head || document.getElementsByTagName('head')[0]
实现document.head属性
的浏览器包括Chrome和Safari5。
字符集属性
HTML5新增了几个与文档字符集有关的属性。其中,charset属性
表示文档中实际使用的字符集,也可以用来指定新字符集。默认情况下,这个属性的值为UTF-16
,但可以通过<meta>元素
、响应头或直接设置charset属性
来修改这个值。
js
alert(document.charset); // 'UTF-16'
document.charset = 'UTF-8'
另一个属性就是defaultCharset
,表示根据默认浏览器及操作系统的设置,当前文档默认的字符集应该是什么。如果文档没有使用默认的字符集,那么charset
和defaultCharset
属性的值可能会不一样。
js
if (document.charset !== document.defalutCharset) {
// ...
}
支持document.charset属性
的浏览器有IE、Safari和Chrome。Firefox支持document.characterset
。
支持document.defaultCharset属性
的浏览器有IE、Safari和Chrome。
自定义数据属性
HTML5规定可以为元素添加非标准的属性,但要添加前缀data-
,目的是为元素提供与渲染无关的信息,或者提供语义信息。这些属性可以任意添加、随便命名,只要以data-
开关即可。
html
<div id="myDiv" data-appId="123456" data-myname="Nicholas"></div>
添加了自定义属性之后,可以通过元素的dataset属性
来访问自定义属性的值。dataset属性
的值是DOMStringMap的一个实例,也就是一个名值对的映射。
js
var div = document.getElementByI('myDiv');
// 获取值
var appId = div.dataset.appId;
var myname = div.dataset.myname;
// 设置值
div.dataset.appId = '654321';
div.dataset.myname = 'Michael';
// 检测是否有某个值
if (div.dataset.othername) {
// ...
}
插入标记
innerHTML 属性
有读模式下,innerHTML属性
返回与调用元素的所有子节点对应的HTML标记。在写模式下,innerHTML元素
会根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点。
html
<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>
对于上面的<div>元素
来说,它的innerHTML属性
会返回下面的字符串:
html
<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>
但是,不同浏览器返回的文本格式会有所不同。
在写模式下,innerHTML
的值会被解析为DOM子树,替换调用元素原来的所有子节点。因为它的值被认为是HTML,所以其中的所有标签都会按照浏览器处理HTML的标准方式转换为元素(同样,这里的转换结果也因浏览器而异)。
js
div.innerHTML = 'Hello world!';
// <div id="content">Hello world!</div>
div.innerHTML = 'Hello world & welcome, <b>"reader"!</b>';
// <div id="content">Hello world & welcome, <b>"reader"!</b></div>
使用innerHTML属性
也有一些限制。
在大多数浏览器中,通过innerHTML
插入<script>元素
并不会执行其中的脚本。
并不是所有元素都支持innerHTML属性
:<col>、<colgroup>、<frameset>、<head><html>、<style>、<table>、<tbody>、<thead>、<tfoot>和<tr>
。此外,在IE8及更早版本中,<title>元素
也没有innerHTML属性
。
无论什么时候,只要使用innerHTML
从外部插入HTML,都应先以可靠的方式处理HTML。IE8为此提供了window.toStaticHTML()
方法,这个方法接收一个参数,即一个HTML字符串,返回一个经过无害处理后的版本。
outerHTML属性
在读模式下,outerHTML
返回调用它的元素及所有子节点的HTML标签。在写模式下,outerHTML
会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换调用元素。
js
<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>
如果在<div>元素
上调用outerHTML
,会返回与上面相同的代码,包括<div>
本身。
使用outerHTML属性
以下面这种方式设置值:
js
div.outerHTML = '<p>This is a paragraph.</p>';
// 等同于
var p = document.createElement('p');
p.appendChild(document.createTextNode('This is a paragraph.));
div.parentNodes.replaceChild(p, div);
支持outerHTML属性
的浏览器有IE4+、Safari4+、Chorme和Opera8+。Firefox7及之前所有版本都不支持。
insertAdjcentHTML()
插入标准的最后一个新增方式是insertAdjcentHTML()
。这个方法接收两个参数:插入位置和要插入HTML文本。第一个参数必须是下列值之一:
- ‘beforebegin’:在当前元素之前插入
- ‘afterbegin’:在当前元素之后插入或在第一个子元素之前插入
- ‘beforeend’:在当前元素之后插入或在最后一个子元素之后插入
- ‘afterend’: 在当前元素之后插入
js
element.insertAdjcentHTML('beforebegin', '<p>Hello world.</p>')
支持insertAdjcentHTML()
的浏览器有IE、Firefox8+、Safari、Opera和Chrome。
scrollIntoView()
scrollIntoView()
可以在所有HTML元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中。如果给这个方法传true
作为参数,或者不传入任何参数,那么窗口滚动之后会让调用元素的顶部与视口顶部尽可能。如果传入false
作为参数,调用元素会尽可能全部出现在视口中,(可能的话,调用元素的底部会与视口底部平齐。)不过顶部不一定平齐。
js
// 让元素可见
document.forms[0].scrollIntoView();
支持scrollIntoView()
方法的浏览器有IE、Firefox、Safari和Opera。
专有扩展
文档模式
IE8引入了一个新的概念叫“文档模式”(document mode)。页面的文档模式决定了可以使用什么功能。到了IE9,总共有以下4种文档模式:
- IE5
- IE7
- IE8
- IE9
要强制浏览器以某种模式渲染页面,可以使用HTTP头部信息X-UA-Compatible
,或通过等价的<meta>标签
来设置。
html
<meta http-equiv="X-UA-Compatible" content="IE=IEVersion">
注意,这里IE的版本(IEVersion)有以下不同的值,而且这些值并不一定与上述4种文档模式对应。
- Edge:始终以最新的文档模式来渲染,忽略文档类型声明。
- EmulateIE9:如果有文档类型声明,则以IE9标准模式渲染页面,否则将文档模式设置为IE5。
- EmulateIE8:如果有文档类型声明,则以IE8标准模式渲染页面,否则将文档模式设置为IE5。
- EmulateIE7:如果有文档类型声明,则以IE7标准模式渲染页面,否则将文档模式设置为IE5。
- 9 强制以IE9标准模式渲染页面,忽略文档类型声明。
- 8 强制以IE8标准模式渲染页面,忽略文档类型声明。
- 7 强制以IE7标准模式渲染页面,忽略文档类型声明。
- 5 将文档模式设置为IE5,忽略文档类型声明。
children属性
children属性
是HTMLCollection的实例,只包含元素中的元素子节点。除此之外,children属性
与childNodes
没有什么区别。
js
var childCount = element.children.length;
var firstChild = element.children[0];
支持children属性
的浏览器有IE5、Firefox3.5、Safari2(但有bug)、Safari3(完全支持)、Opera8和Chorme(所有版本)。
contains()
在实际开发中,经常需要知道某个节点是不是另一个节点的后代。contains()
方法就可以做到这点,这个方法接收一个参数,即要检测的后代节点。如果是后代节点返回true,反之false。
js
// 检测body是不是html的后代
alert(document.documentElement.contains(document.body)); // true
支持这个方法的浏览器有IE、Firefox9+、Safari、Opera和Chrome。
使用DOM Level 3 compareDocumentPosition()
也能够确定节点间的关系。支持这个方法的浏览器有IE9+、Firefox、Safari、Opera9.5+和Chrome。这个方法返回一个表示关系的位掩码(bitmask)。
掩码 | 节点关系 |
---|---|
1 | 无关 |
2 | 居前 |
4 | 居后 |
8 | 包含 |
16 | 被包含 |
js
var result = document.documentElement.compareDocumentPosition(document.body);
alert(!!(result & 16)); // 使用两个逻辑非操作符会将数值转换成布尔值。
插入文本
innerText属性
读取值时,innerText
会按照由浅入深的顺序,将子文档树中所有文本拼接起来。写入值时,会删除元素的所有子节点,插入包含相应文本值的文本节点。
outerText属性
读取值时,与innertText
的结果完全一样。写入值时,会替换整个元素(包括子节点)。
滚动
在HTML5将scrollIntoView()
纳入规范之后,仍然还有其他几个专有方法可以在不同的浏览器器使用。下面列出的几个方法都是对HTMLElement类型的扩展,因此可以在所有元素调用。
scrollIntoViewIfNeeded(alignCenter)
:只在当前元素在视口中不可见的情况下,才滚动,让它可见。如果将参数设置为true,则表示尽量将元素显示在视口中部(垂直方向)。Safari和Chrome实现了这个方法。scrollByLines(lineCount)
:将元素的内容滚动指定的行高,lineCount可以是正值,也可以是负值。Safari和Chrome实现了这个方法。scrollByPages(pageCount)
:将元素的内容滚动指定的页面高度,具体高度由元素的高度决定。Safari和Chrome实现了这个方法。
注意:scrollIntoView()
和scrollIntoViewIfNeeded()
的操作对象是元素的容器,而scrollByLines()
和scrollByPages()
影响的则是元素自身。
js
// 将页面主体滚动5行
document.body.scrollByLines(5);
// 在当前元素不可见时,让它进入浏览器的视口
document.images[0].scrollIntoViewIfNeeded();
// 将页面主体往回滚动1页
document.body.scrollByPages(-1);
由于scrollIntoView()
是唯一一个所有浏览器都支持的方法,因此还是这个方法最常用。