尽管 DOM 作为 API 已经非常完善了,但为了实现更多的功能,仍然会有一些标准或专有的扩 展。2008 年之前,浏览器中几乎所有的 DOM 扩展都是专有的。此后,W3C 着手将一些已经 成为事实标准的专有扩展标准化并写入规范当中。 对 DOM 的两个主要的扩展是 Selectors API(选择符 API)和 HTML5。这两个扩展都源自开发社区, 而将某些常见做法及 API 标准化一直是众望所归。此外,还有一个不那么引人瞩目的 Element Traversal (元素遍历)规范,为 DOM 添加了一些属性。虽然前述两个主要规范(特别是 HTML5)已经涵盖了大 量的 DOM 扩展,但专有扩展依然存在。
选择符API
- querySelector()方法
querySelector()方法接收一个 CSS 选择符,返回与该模式匹配的第一个元素,如果没有找到匹
配的元素,返回 null。
//取得 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");
- querySelectorAll()方法
querySelectorAll()方法接收的参数与 querySelector()方法一样,都是一个 CSS 选择符,但
返回的是所有匹配的元素而不仅仅是一个元素。这个方法返回的是一个 NodeList 的实例。
//取得某<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");
- matchesSelector()方法
Selectors API Level 2 规范为 Element 类型新增了一个方法 matchesSelector()。这个方法接收
一个参数,即 CSS 选择符,如果调用元素与该选择符匹配,返回 true;否则,返回 false
if (document.body.matchesSelector("body.page1")){
//true
}
元素遍历API
这个作为了解:
对于元素间的空格,IE9 及之前版本不会返回文本节点,而其他所有浏览器都会返回文本节点。这样,就导致了在使用 childNodes 和 firstChild 等属性时的行为不一致。为了弥补这一差异,而同时又保持 DOM 规范不变Element Traversal 规范(www.w3.org/TR/ElementTraversal/)新定义了一组属性。
Element Traversal API 为 DOM 元素添加了以下 5 个属性:
- childElementCount:返回子元素(不包括文本节点和注释)的个数
- firstElementChild:指向第一个子元素;firstChild 的元素版
- lastElementChild:指向最后一个子元素;lastChild 的元素版
- previousElementSibling:指向前一个同辈元素;previousSibling 的元素版
- nextElementSibling:指向后一个同辈元素;nextSibling 的元素版
HTML5
对于传统 HTML 而言,HTML5 是一个叛逆。所有之前的版本对 JavaScript 接口的描述都不过三言 两语,主要篇幅都用于定义标记,与 JavaScript 相关的内容一概交由 DOM 规范去定义。而 HTML5 规范则围绕如何使用新增标记定义了大量 JavaScript API。其中一些 API 与 DOM 重叠,定义了浏览器应该支持的 DOM 扩展
与类相关的扩充
1、getElementsByClassName()方法
//取得所有类中包含"username"和"current"的元素,类名的先后顺序无所谓
var allCurrentUsernames = document.getElementsByClassName("username current");
//取得 ID 为"myDiv"的元素中带有类名"selected"的所有元素
var selected = document.getElementById("myDiv").getElementsByClassName("selected");
2、classList 属性
在操作类名时,需要通过 className 属性添加、删除和替换类名。
<div class="bd user disabled">...</div>
这个<div>元素一共有三个类名。要从中删除一个类名,需要把这三个类名拆开,删除不想要的那
个,然后再把其他类名拼成一个新字符串。请看下面的例子。
//删除"user"类
//首先,取得类名字符串并拆分成数组
var classNames = div.className.split(/\s+/);
//找到要删的类名
var pos = -1, i, len;
for (i=0, len=classNames.length; i < len; i++){
if (classNames[i] == "user"){
pos = i;
break;
}
}
//删除类名
classNames.splice(i,1);
//把剩下的类名拼成字符串并重新设置
div.className = classNames.join(" ");
焦点管理
HTML5 也添加了辅助管理 DOM 焦点的功能。首先就是 document.activeElement 属性,这个
属性始终会引用 DOM 中当前获得了焦点的元素。元素获得焦点的方式有页面加载、用户输入(通常是
通过按 Tab 键)和在代码中调用 focus()方法。来看几个例子。
var button = document.getElementById("myButton");
button.focus();
alert(document.activeElement === button);//true
默认情况下,文档刚刚加载完成时,document.activeElement 中保存的是 document.body 元素的引用。文档加载期间,document.activeElement 的值为 null。另外就是新增了 document.hasFocus()方法,这个方法用于确定文档是否获得了焦点。
var button = document.getElementById("myButton");
button.focus();
alert(document.hasFocus()); //true
小结:
通过检测文档是否获得了焦点,可以知道用户是不是正在与页面交互。
查询文档获知哪个元素获得了焦点,以及确定文档是否获得了焦点,这两个功能最重要的用途是提高 Web 应用的无障碍性。无障碍 Web 应用的一个主要标志就是恰当的焦点管理,而确切地知道哪个元素获得了焦点是一个极大的进步,至少我们不用再像过去那样靠猜测了。
HTMLDocument的变化
readyState 属性
IE4 最早为 document 对象引入了 readyState 属性。然后,其他浏览器也都陆续添加这个属性,
最终 HTML5 把这个属性纳入了标准当中。Document 的 readyState 属性有两个可能的值:
loading,正在加载文档;
complete,已经加载完文档。兼容模式
自从 IE6 开始区分渲染页面的模式是标准的还是混杂的,检测页面的兼容模式就成为浏览器的必要
功能。IE 为此给 document 添加了一个名为 compatMode 的属性,这个属性就是为了告诉开发人员浏
览器采用了哪种渲染模式。就像下面例子中所展示的那样,在标准模式下,document.compatMode 的
值等于”CSS1Compat”,而在混杂模式下,document.compatMode 的值等于”BackCompat”。if (document.compatMode == "CSS1Compat"){
alert("Standards mode");
} else {
alert("Quirks mode");
}
字符集属性
HTML5 新增了几个与文档字符集有关的属性。其中,charset 属性表示文档中实际使用的字符集,
也可以用来指定新字符集。默认情况下,这个属性的值为”UTF-16”,但可以通过元素、响应头
部或直接设置 charset 属性修改这个值。来看一个例子:
alert(document.charset); //"UTF-16"
document.charset = "UTF-8";
自定义数据属性
HTML5 规定可以为元素添加非标准的属性,但要添加前缀 data-,目的是为元素提供与渲染无关的
信息,或者提供语义信息。这些属性可以任意添加、随便命名,只要以 data-开头即可。来看一个例子。
<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>
添加了自定义属性之后,可以通过元素的 dataset 属性来访问自定义属性的值。dataset 属性的
值是 DOMStringMap 的一个实例,也就是一个名值对儿的映射。在这个映射中,每个 data-name 形式
的属性都会有一个对应的属性,只不过属性名没有 data-前缀(比如,自定义属性是 data-myname,
那映射中对应的属性就是 myname)
//本例中使用的方法仅用于演示
var div = document.getElementById("myDiv");
//取得自定义属性的值
var appId = div.dataset.appId;
var myName = div.dataset.myname;
//设置值
div.dataset.appId = 23456;
div.dataset.myname = "Michael";
//有没有"myname"值呢?
if (div.dataset.myname){
alert("Hello, " + div.dataset.myname);
}
插入标记
innerHTML 属性
div.innerHTML = "Hello world!" 与 div.innerText ="Hello world!"
outerHTML 在读模式下,outerHTML 返回调用它的元素及所有子节点的 HTML 标签。在写模式下,
会根据指定的 HTML 字符串创建新的 DOM 子树,然后用这个 DOM 子树完全替换调用元素。下面是一
个例子:
<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>
使用 outerHTML 属性以下面这种方式设置值:
div.outerHTML = "<p>This is a paragraph.</p>";
scrollIntoView()方法
如何滚动页面也是 DOM 规范没有解决的一个问题。为了解决这个问题,浏览器实现了一些方法,
以方便开发人员更好地控制页面滚动。在各种专有方法中,HTML5 最终选择了 scrollIntoView()作
为标准方法。
scrollIntoView()可以在所有 HTML 元素上调用,通过滚动浏览器窗口或某个容器元素,调用
元素就可以出现在视口中。如果给这个方法传入 true 作为参数,或者不传入任何参数,那么窗口滚动
之后会让调用元素的顶部与视口顶部尽可能平齐。如果传入 false 作为参数,调用元素会尽可能全部
出现在视口中,(可能的话,调用元素的底部会与视口顶部平齐。)不过顶部不一定平齐,例如:
//让元素可见
document.forms[0].scrollIntoView();
当页面发生变化时,一般会用这个方法来吸引用户的注意力。实际上,为某个元素设置焦点也会导
致浏览器滚动并显示出获得焦点的元素。
专有扩展
1、文档模式 document mode
2、children 属性
3、contains()方法
在实际开发中,经常需要知道某个节点是不是另一个节点的后代。IE 为此率先引入了 contains()
方法,以便不通过在 DOM 文档树中查找即可获得这个信息。调用 contains()方法的应该是祖先节点,
也就是搜索开始的节点,这个方法接收一个参数,即要检测的后代节点。如果被检测的节点是后代节点,
该方法返回 true;否则,返回 false。以下是一个例子:
alert(document.documentElement.contains(document.body)); //true
4、插入文本
innerText 属性:
通过 innertText 属性可以操作元素中包含的所有文本内容,包括子文档树中的文本。在通过
innerText 读取值时,它会按照由浅入深的顺序,将子文档树中的所有文本拼接起来。在通过
innerText 写入值时,结果会删除元素的所有子节点,插入包含相应文本值的文本节点。
<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.innerText = "Hello world!";
<div id="content">Hello world!</div>
5、滚动
- scrollIntoViewIfNeeded(alignCenter):只在当前元素在视口中不可见的情况下,才滚
动浏览器窗口或容器元素,最终让它可见。如果当前元素在视口中可见,这个方法什么也不做。
如果将可选的 alignCenter 参数设置为 true,则表示尽量将元素显示在视口中部(垂直方向)。
Safari 和 Chrome 实现了这个方法
- scrollByLines(lineCount):将元素的内容滚动指定的行高,lineCount 值可以是正值,
也可以是负值。Safari 和 Chrome 实现了这个方法
- scrollByPages(pageCount):将元素的内容滚动指定的页面高度,具体高度由元素的高度决
定。Safari 和 Chrome 实现了这个方法
希望大家要注意的是,scrollIntoView()和 scrollIntoViewIfNeeded()的作用对象是元素的容器,而 scrollByLines()和 scrollByPages()影响的则是元素自身。下面还是来看几个示例吧。
//将页面主体滚动 5 行
document.body.scrollByLines(5);
//在当前元素不可见的时候,让它进入浏览器的视口
document.images[0].scrollIntoViewIfNeeded();
//将页面主体往回滚动 1 页
document.body.scrollByPages(-1);