十七

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

    你可能感兴趣的:(十七)