DOM扩展和HTML5的API

TOC
  1. 1. 选择符API
    1. 1.1. querySelector()
    2. 1.2. querySelectorAll()
    3. 1.3. matchesSelector()
  2. 2. 元素遍历
  3. 3. HTMl5新增API
    1. 3.1. getElementsByClassName()
      1. 3.1.1. 方法实现
      2. 3.1.2. classList 属性
    2. 3.2. 焦点管理
      1. 3.2.1. activeElement
      2. 3.2.2. hasFocus()
    3. 3.3. HTMLDocument
      1. 3.3.1. readystate
      2. 3.3.2. 兼容模式
      3. 3.3.3. head属性
    4. 3.4. 字符集属性
    5. 3.5. 插入标记
      1. 3.5.1. innerHTML

选择符API

querySelector()querySelectorAll(),目前 IE 8+、 Firefox 3.5+、 Safari 3.1+、 Chrome 和 Opera 10+都支持这两个方法.

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 的实例

具体来说,返回的值实际上是带有所有属性和方法的 NodeList,而其底层实现则类似于一组元素的快照,而非不断对文档进行搜索的动态查询。这样实现可以避免使用NodeList对象通常会引起的大多数性能问题。

只要传给 querySelectorAll()方法的 CSS 选择符有效,该方法都会返回一个 NodeList 对象,而不管找到多少匹配的元素。如果没有找到匹配的元素, NodeList 就是空的。

matchesSelector()

Selectors API Level 2 规范为 Element 类型新增了一个方法 matchesSelector()。这个方法接收一个参数,即 CSS 选择符,如果调用元素与该选择符匹配,返回 true;否则,返回 false。看例子。

if (document.body.matchesSelector("body.page1")){
    //true
}

在取得某个元素引用的情况下,使用这个方法能够方便地检测它是否会被 querySelector()或querySelectorAll()方法返回。截至 2011 年年中,还没有浏览器支持 matchesSelector()方法;不过,也有一些实验性的实现。

IE 9+通过 msMatchesSelector()支持该方法,Firefox 3.6+通过 mozMatchesSelector()支持该方法,

Safari 5+和 Chrome 通过 webkitMatchesSelector()支持该方法。因此,如果你想使用这个方法,最好是编写一个包装函数。

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 规范(www.w3.org/TR/ElementTraversal/)新定义了一组属性。

Element Traversal API 为 DOM 元素添加了以下 5 个属性。

  • 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新增API

本节只讨论DOM相关

getElementsByClassName()

方法实现

这是以前模拟实现getElementsByClassName的思路:

K.prototype.getElementsByClassName=function(cls,id){
    var node = null;
    if(arguments.length==2){
        node = document.getElementById(id);
    }else{
        node = document;
    }
    var all = node.getElementsByTagName('*');
        for(var i=0;i<all.length;i++){
            if(all[i].className==cls){
                this.elements.push(all[i]);
            }
        }
    return this;
}

这个方法接收一个参数,即一个包含一或多个类名的字符串,返回带有指定类的所有元素的NodeList。传入多个类名时,类名的先后顺序不重要。

//取得所有类中包含"username"和"current"的元素,类名的先后顺序无所谓
var allCurrentUsernames = document.getElementsByClassName("username current");
//取得 ID 为"myDiv"的元素中带有类名"selected"的所有元素
var selected = document.getElementById("myDiv").getElementsByClassName("selected");

使用这个方法可以更方便地为带有某些类的元素添加事件处理程序,从而不必再局限于使用 ID 或标签名。不过别忘了,因为返回的对象是 NodeList,所以使用这个方法与使用 getElementsByTagName()以及其他返回 NodeList 的 DOM 方法都具有同样的性能问题。

支持 getElementsByClassName()方法的浏览器有 IE 9+、 Firefox 3+、 Safari 3.1+、 Chrome 和Opera 9.5+。

classList 属性

如果要为某一个元素增删其中的一个class名称,通过className来实现的话,就必须有一个匹配,删除,合并的过程:

<div class="bd user disabled">...</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 新增了一种操作类名的方式,可以让操作更简单也更安全,那就是为所有元素添加classList属性。这个classList属性是新集合类型DOMTokenList 的实例。与其他DOM集合类似DOMTokenList有一个表示自己包含多少元素的length属性,而要取得每个元素可以使用 item()方法,也可以使用方括号语法。此外,这个新类型还定义如下方法。

  • add(value)
  • contains(value) //true or false
  • remove(value)
  • toggle(value)

有了classList属性,除非你需要全部删除所有类名,或者完全重写元素的 class属性,否则也就用不到 className 属性了。

不过,支持 classList 属性的浏览器有 Firefox 3.6+和 Chrome。

焦点管理

HTML5 也添加了辅助管理 DOM 焦点的功能。首先就是document.activeElement属性,这个属性始终会引用 DOM 中当前获得了焦点的元素。元素获得焦点的方式有页面加载、用户输入(通常是通过按 Tab 键)和在代码中调用 focus()方法。

activeElement

来看几个例子:

var button = document.getElementById("myButton");
 button.focus();
 alert(document.activeElement === button); //true

默认情况下,文档刚刚加载完成时, document.activeElement 中保存的是 document.body 元素的引用。文档加载期间, document.activeElement 的值为 null。

hasFocus()

这个方法用于确定文档是否获得了焦点。

var button = document.getElementById("myButton");
button.focus();
alert(document.hasFocus()); //true

通过检测文档是否获得了焦点,可以知道用户是不是正在与页面交互。查询文档获知哪个元素获得了焦点,以及确定文档是否获得了焦点,这两个功能最重要的用途是提高Web应用的无障碍性。无障碍Web应用的一个主要标志就是恰当的焦点管理,而确切地知道哪个元素获得了焦点是一个极大的进步,至少我们不用再像过去那样靠猜测了。

实现了这两个属性的浏览器的包括 IE 4+、 Firefox 3+、 Safari 4+、 Chrome 和 Opera 8+。

HTMLDocument

readystate

IE4 最早为 document 对象引入了 readyState属性。然后,其他浏览器也都陆续添加这个属性,最终 HTML5 把这个属性纳入了标准当中。Document的readyState属性有两个可能的值:

  • loading
  • complete

document.readyState 属性的基本用法如下:

if (document.readyState == "complete"){
//执行操作
}

支持 readyState 属性的浏览器有 IE4+、 Firefox 3.6+、 Safari、 Chrome 和 Opera 9+。

兼容模式

自从 IE6 开始区分渲染页面的模式是标准的还是混杂的,检测页面的兼容模式就成为浏览器的必要功能。 IE 为此给 document 添加了一个名为 compatMode 的属性,这个属性就是为了告诉开发人员浏览器采用了哪种渲染模式。就像下面例子中所展示的那样,在标准模式下, document.compatMode 的值等于”CSS1Compat”,而在混杂模式下, document.compatMode 的值等于”BackCompat”。

if (document.compatMode == "CSS1Compat"){
        alert("Standards mode");
} else {
    alert("Quirks mode");
}

现已成为HTML5标准.

head属性

作为对 document.body 引用文档的<body>元素的补充, HTML5 新增了 document.head 属性,引用文档的<head>元素。

配合兼容:

var head = document.head || document.getElementsByTagName("head")[0];

字符集属性

略(默认情况是UTF-16,但是实际测试是windows-1252,也许和编辑器的转码有关)

###自定义属性
HTML5 规定可以为元素添加非标准的属性,但要添加前缀 data-,目的是为元素提供与渲染无关的信息,或者提供语义信息。这些属性可以任意添加、随便命名,只要以 data-开头即可。
如:

<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>

添加了自定义属性之后,可以通过元素的 dataset属性来访问自定义属性的值。 这个属性的值是 DOMStringMap 的一个实例,也就是一个名值对的映射。

如:

var div = document.getElementById("myDiv");
//动态设置属性
var appId = div.dataset.appId;
var myName = div.dataset.myname;
//添加值
div.dataset.appId = 23456;
div.dataset.myname = "Michael";

插入标记

innerHTML

要注意的地方:

  • 在IE和标准情况下写入的标签大小写不一样
  • 包含和不包含标签的情况解析是不一样的(*)
  • 插入脚本(*)

关键:
使用innerHTML属性也有一些限制。比如,在大多数浏览器中,通过 innerHTML 插入<script>元素并不会执行其中的脚本。 IE8 及更早版本是唯一能在这种情况下执行脚本的浏览器,但必须满足一些条件。一是必须为<script>元素指定 defer 属性,二是<script>元素必须位于(微软所谓的) “有作用域的元素”(scoped element)之后。

<script>元素被认为是“无作用域的元素”(NoScope element),也就是在页面中看不到的元素,与<style>元素或注释类似。如果通过 innerHTML 插入的字符串开头就是一个“无作用域的元素”,那么 IE 会在解析这个字符串前先删除该元素。换句话说,以下代码达不到目的:

div.innerHTML = "<script defer>alert('hi');<\/script>"; //无效

此时, innerHTML 字符串一开始(而且整个)就是一个“无作用域的元素”,所以这个字符串会变成空字符串。如果想插入这段脚本,必须在前面添加一个“有作用域的元素”,可以是一个文本节点,也可以是一个没有结束标签的元素如<input>。例如,下面这几行代码都可以正常执行:(XSS技巧)

div.innerHTML = "_<script defer>alert('hi');<\/script>";
div.innerHTML = "<div>&nbsp;</div><script defer>alert('hi');<\/script>";
div.innerHTML = "<input type=\"hidden\"><script defer>alert('hi');<\/script>";

第一行代码会在<script>元素前插入一个文本节点。事后,为了不影响页面显示,你可能需要移除这个文本节点。第二行代码采用的方法类似,只不过使用的是一个包含非换行空格的<div>元素。如果仅仅插入一个空的<div>元素,还是不行;必须要包含一点儿内容,浏览器才会创建文本节点。

同样,为了不影响页面布局,恐怕还得移除这个节点。第三行代码使用的是一个隐藏的<input>域,也能达到相同的效果。不过,由于隐藏的<input>域不影响页面布局,因此这种方式在大多数情况下都是首选。

大多数浏览器都支持以直观的方式通过 innerHTML 插入<style>元素,例如:

div.innerHTML = "<style type=\"text/css\">body {background-color: red; }</style>";

但在 IE8 及更早版本中,<style>也是一个“没有作用域的元素”,因此必须像下面这样给它前置一个“有作用域的元素”:

div.innerHTML = "_<style type=\"text/css\">body {background-color: red; }</style>";
div.removeChild(div.firstChild);

并不是所有元素都支持 innerHTML 属性。不支持 innerHTML 的元素有:<col>、`

访客评论