DOM扩展
对DOM的两个主要的扩展是Selectors API(选择符API)和HTML5。
选择符API
Selectors API是由W3C发起指定的一个标准,致力于让浏览器原生支持CSS查询。所有实现这一功能的JavaScript库都会写一个基础的CSS解析器,然后再使用已有的DOM方法查询文档并找到匹配的节点。
Selectors API Level 1的核心是两个方法:querySelector()和querySelectorAll()。在兼容的浏览器中,可以通过Document和Element类型的实例调用它们。目前已完全支持Selectors API Level 1的浏览器有IE 8+..Firefox 3.5+..Safari 3.1+..Chrome .. Opera 10+。
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");
通过Document类型调用querySelector()方法时,会在文档元素的范围内查找匹配的元素。而通过Element类型调用querySelector()方法时,只会在该元素后代元素的范围内查找匹配的元素。
CSS选择符可以简单也可以复杂,视情况而定。如果传入了不被支持的选择符,querySelector()会抛出错误。
querySelectorAll()方法
querySelectorAll()方法接收的参数与querySelector()方法一样,都是一个CSS选择符,但返回的是所有匹配的元素而不仅仅是一个元素。这个方法返回的是一个NodeList的实例。
能够调用querySelectorAll()方法的类型包括Document、
DocumentFragment 和 Element:
//取得某中的所有类似于getElementsByTagName("em")
var ems = document.getElementById("myDiv").querySelectorAll("em");
//取得类为"selected"的所有元素
var selecteds = document.querySelectorAll(".selected");
//取得所有元素中的所有元素
var strongs = document.querySelectorAll("p strong");
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()。这个方法接收一个参数,即CSS选择符,如果调用元素与该元素符匹配,返回true;否则false。
if (document.body.matchesSelector("body.page1")){
//true
}
在取得某个元素引用的情况下,使用这个方法能够方便地检测它是否会被querySelector()或querySelectorAll()方法返回。
截止2011年年中,还没有浏览器支持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")) {
//执行操作
}
元素遍历
对于元素间的空格,IE9及之前版本不会返回文本节点,而其他所有浏览器都会返回文本节点。这样,就导致了在使用childNodes和firstChild等属性时的行为不一致。为了弥补这一差异,而同时又保持DOM规范不变,Element Traversal规范新定义了一组属性。
- childElementCount:返回子元素(不包括文本节点和注释)的个数。
- firstElementChild:指向第一个子元素;firstChild的元素版。
- lastElementChild:指向最后一个子元素;lastChild的元素版。
- previousElementSibling:指向前一个同辈元素;previousSibling的元素版。
- nextElementSibling:指向后一个同辈元素;nextSibling的元素版。
var i,
len,
child = element.firstChild;
while (child != element.lastChild) {
if (child.nodeType == 1) { //检查是不是元素
processChild(child);
}
child = child.nextSibling;
}
而使用Element Traversal新增的元素,代码会更简洁。
var i,
len,
child = element.firstElementChild;
while (child != element.lastElementChild) {
processChild(child); //已知其是元素
child = child.nextElementSibling;
}
支持Element Traversal规范的浏览器有IE 9+Firefox 3.5+Safari 4+Chrome Opera 10+。
HTML5
与类相关的扩充
1、getElementsByClassName()方法
//取有所有类中包含"username"和"current"的元素,类名的先后顺序无所谓
var allCurrentUsernames = document.getElementsByClassName("username current");
//取得ID 为"myDiv"的元素中带有类名"selected"的所有元素
var selected = document.getElementById("myDiv").getElementsByClassName("selected");
IE 9+..Firefox 3+..Safari 3.1+..Chrome ..Opera 9.5+。
2、classList属性
...
//删除"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新增了一种操作类名的方式,可以让操作更简单也更安全,上面那么多行代码用下面一行代码就可以代替了:
div.classList.remove("user");
///删除“disabled”类
div.classList.remove("disabled");
//添加"current"类
div.classList.add("current");
//切换"user"类
div.classList.toggle("user");
//确定元素中是否包含既定的类名
if (div.classList.contains("bd") && !div.classList.contains("disabled")) {
//执行操作
)
//迭代类名
for (var i = 0, len = div.classList.length; i < len; i++) {
doSomething(div.classList[i]);
}
有Firefox 3.6+ Chrome。
焦点管理
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
通过检测文档是否获得了焦点,可以知道用户是不是正在与页面交互。
IE 4+..Firefox 3+..Safari 4+..Chrome .. Opera 8+。
HTMLDocument的变化
1、readyState属性
- loading,正在加载文档;
- complete,已经加载完文档。
使用document.readyState的最恰当方式,就是通过它来实现一个指示文档已经加载完成的指示器。在这个属性得到广泛支持之前,要实现这样一个指示器,必须借助onload事件处理程序设置一个标签,表明文档已经加载完毕。
if (document.readyState == "complete"){
//执行操作
}
IE4+..Firefox 3.6+..Safari..Chrome .. Opera 9+。
2、兼容模式
自从IE6开始区分渲染页面的模式是标准的还是混杂的,检测页面的兼容模式就称为浏览器的必要功能。IE为此给document添加了一个名为compatMode的属性。在标准模式下,document.compatMode的值等于“CSS1Compat”,而在混杂模式下,document.compatMode的值等于“BackCompat”。
if (document.compatMode == "CSS1Compat") {
alert("Standards mode");
} else {
alert("Quirks mode");
}
后来,陆续实现这个属性的浏览器有FirefoxSafari 3.1+Opera Chrome。
3、head属性
作为对document.body引用文档的
元素的补充,HTML5新增了document.head属性,引用文档的元素。要引用文档的元素,可以结合使用这个属性和另一种后备方法。
var head = document.head || document.getElementsByTagName("head")[0];
Chrome Safari 5
字符集属性
alert(document.charset); //"UTF-16"
document.charset = "UTF-8";
if (document.charset != document.defaultCharset){
alert("Custom character set being used.");
}
支持document.charset属性的浏览器有IE、Firefox、Safari、Opera和Chrome。支持document.defaultCharset属性的浏览器有IE、Safari和Chrome。
自定义数据属性
HTML5规定可以为元素添加非标准的属性,但要添加前缀data-,目的是为元素提供与渲染无关的信息,或者提供语义信息:
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);
}
插入标记
1、innerHTML属性
div.innerHTML = "Hello world!";
为innerHTML设置的包含HTML的字符串值与解析后innerHTML的值大不相同:
div.innerHTML = "Hello & welcome, \"reader\"!";
以上操作得到的结果:
Hello & welcome, "reader"!
2、outerHTML属性
在读模式下,outerHTML返回调用它的元素及所有子节点的HTML标签。在写模式下,outerHTML会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换调用元素:
div.outerHTML = "This is a paragraph.
";
这行代码完成的操作与下面这些DOM脚本代码一样:
var p = document.createElement("p");
p.appendChild(document.createTextNode("This is a paragraph."));
div.parentNode.replaceChild(p, div);
IE4+..Safari 4+..Chrome .. Opera 8+。Firefox7及之前版本都不支持outerHTML属性。
3、insertAdjacentHTML()方法
//作为前一个同辈元素插入
element.insertAdjacentHTML("beforebegin", "Hello world!
");
//作为第一个子元素插入
element.insertAdjacentHTML("afterbegin", "Hello world!
");
//作为最后一个子元素插入
element.insertAdjacentHTML("beforeend", "Hello world!
");
//作为后一个同辈元素插入
element.insertAdjacentHTML("afterend", "Hello world!
");
IEFirefox 8+SafariOpera Chrome
4、内存和性能问题
在使用innerHTML、outerHTML属性和insertAdjacentHTML()方法时,最好先手工删除要被替换的元素的所有事件处理程序和JavaScript对象属性。使用这几个属性,特别是innerHTML,仍然还是可以为我们提供很多便利。一般来说,在插入大量新HTML标记时,使用innerHTML属性与通过多次DOM操作先创建节点指定它们之间的关系相比,效率要高得多。这是因为在设置innerHTML或outerHTML时,就会创建一个HTML解析器。这个解析器是在浏览器级别的代码(通常是C++)基础上运行的,因此比执行JavaScript快的多。不可避免地,创建和销毁HTML解析器也会带来性能损失,所以最好能够将设置innerHTML或outerHTML的次数控制在合理的范围内:
var itemsHtml = "";
for (var i=0, len=values.length; i < len; i++){
itemsHtml += "" + values[i] + " ";
}
ul.innerHTML = itemsHtml;
这个例子的效率要高得多,因为它只对innerHTML执行了一次赋值操作。
scrollIntoView()
该方法可以在所有HTML元素上调用。如果传入true作为参数,或者不传,那么窗口滚动之后会让调用元素的顶部与视口顶部尽可能平齐。如果传入false,调用元素会尽可能全部出现在视口:
//让元素可见
document.forms[0].scrollIntoView();
children属性
由于IE9之前的版本与其他浏览器在处理文本节点中的空白符时有差异,因此就出现了children属性。
var childCount = element.children.length;
var firstChild = element.children[0];
contains()方法
alert(document.documentElement.contains(document.body)); //true
IEFirefox 9+SafariOpera Chrome
插入文本
1、innerText属性
div.innerText = "Hello world!";
2、outerText属性
在读取文本值时,outerText与innerText的结果完全一样。但在写模式下,outerText就完全不同了:outerText不只是替换调用它的元素的子节点,而且会替换整个元素:
div.outerText = "Hello world!";
这行代码实际上相当于如下两行代码:
var text = document.createTextNode("Hello world!");
div.parentNode.replaceChild(text, div);
IE4+Safari 3+Opera 8+ Chrome