DOM扩展和HTML5的API

提醒:本文发布于 3423 天前,文章内容可能 因技术时效性过期 或 被重新修改,请谨慎参考。

选择符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

用法如下:

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>、`` <colgroup>
<frameset><head><html><style><table><tbody><thead><tfoot><tr>。此外,在 IE8 及更早版本中, <title>元素也没有 innerHTML 属性。

注意: 在xHTML中,代码格式有非常严格的限制,否则会静默失败.

由于这种方式给XSS攻击带来了方便,因此赋值给div.inneeHTML的字符串必须经过过滤.

IE8(只是IE8?)为此提供了window.toStaticHTML()方法

var text = "<a href=\"#\" onclick=\"alert('hi')\">Click Me</a>";
var sanitized = window.toStaticHTML(text); //Internet Explorer 8 only
alert(sanitized); //"<a href=\"#\">Click Me</a>"

*目前只有IE8支持这个方法

outerHTML

和innerHTML类似,但是包括本身获取的那个节点的标签.

insertAdjacentHTML()

这是一个全兼容方法.接受两个参数,第一个参数必须是下列值之一:

  • “beforebegin” //作为前一个兄弟元素插入
  • “afterbegin” //第一个子元素
  • “beforeend” //最后一个子元素
  • “afterend” //后一个兄弟元素

第二个参数就是要插入的节点字符串

内存和性能

设置 innerHTML 或 outerHTML 时,就会创建一个 HTML解析器。这个解析器是在浏览器级别的代码(通常是 C++编写的)基础上运行的,因此比执行 JavaScript快得多。不可避免地,创建和销毁 HTML解析器也会带来性能损失,所以最好能够将设置 innerHTML或 outerHTML 的次数控制在合理的范围内.

scrollIntoView()

用法:

document.getElementById('btn').onclick=function(){
document.getElementById('test').scrollIntoView();
}

这个方法全兼容

##专有扩展
就是浏览器厂商为自己的浏览器做专有的功能扩展
###文档模式
IE8引入文档模式,决定了你可以使用哪个级别的 CSS,可以在 JavaScript 中使用哪些 API,以及如何对待文档类型(doctype)。
到了IE9,总共有四种文档模式

  • IE5:以混杂模式渲染页面(IE5 的默认模式就是混杂模式)。 IE8 及更高版本中的新功能都无法使用。
  • IE7:以 IE7 标准模式渲染页面。 IE8 及更高版本中的新功能都无法使用。
  • IE8:以 IE8 标准模式渲染页面。 IE8 中的新功能都可以使用,因此可以使用 Selectors API、更多CSS2 级选择符和某些 CSS3 功能,还有一些 HTML5 的功能。不过 IE9 中的新功能无法使用。
  • IE9:以 IE9 标准模式渲染页面。 IE9 中的新功能都可以使用,比如 ECMAScript 5、完整的 CSS3
    以及更多 HTML5 功能。

要强制浏览器以某种模式渲染页面,可以使用 HTTP 头部信息 X-UA-Compatible,或通过等价的<meta>标签来设置:

<meta http-equiv="X-UA-Compatible" content="IE=IEVersion"> 	//通常IEVersion我们使用Edge

没有规定说必须在页面中设置 X-UA-Compatible。默认情况下,浏览器会通过文档类型声明来确定是使用最佳的可用文档模式,还是使用混杂模式。

通过 document.documentMode 属性可以知道给定页面使用的是什么文档模式。这个属性是 IE8中新增的,它会返回使用的文档模式的版本号(在 IE9 中,可能返回的版本号为 5、 7、 8、 9):

alert(document.documentMode);

不过目前, document.documentMode似乎用得不多.

children属性

由于 IE9 之前的版本与其他浏览器在处理文本节点中的空白符时有差异,因此就出现了 children属性。这个属性是 HTMLCollection 的实例,只包含元素中同样还是元素的子节点。除此之外,children 属性与 childNodes 没有什么区别,即在元素只包含元素子节点时,这两个属性的值相同。

下面是访问 children 属性的示例代码:

var childCount = element.children.length;
var firstChild = element.children[0];

IE9及以后就不包含注释节点了.

contain()

IE率先引入这个方法:

alert(document.documentElement.contains(document.body)); //true

这是全兼容方法

DOM3里面有个compareDocumentPosition()方法,用于确定两个节点之间的关系.
用法:

document.documentElement.compareDocumentPosition(document.body);	//20, 相关数值相加
掩码 节点关系
1 无关,给定节点不存在当前文档
2 居前
4 居后
8 包含
16 被包含

如果要检测一个节点是不是另一个节点的父节点可以通过以上两个方法,再结合parentNode属性,就可以封装成一个检测方法.

function contains(refNode, otherNode){
if (typeof refNode.contains == "function" &&
(!client.engine.webkit || client.engine.webkit >= 522)){
//针对safari版本问题
return refNode.contains(otherNode);
} else if (typeof refNode.compareDocumentPosition == "function"){
return !!(refNode.compareDocumentPosition(otherNode) & 16);
} else {
var node = otherNode.parentNode;
do {
if (node === refNode){
return true;
} else {
node = node.parentNode;
}
} while (node !== null);
return false;
}
}

插入文本

innerHMLouterHTML都被纳入了HTML5规范.但是innerTextouterText则没有

innerText

innerText永远只会生成当前节点的一个子文本节点

如果:

div.innerText = "Hello & welcome, <b>\"reader\"!</b>";

运行以上代码之后,会以文本的形式输出所有标签.

只有火狐不支持innerText,但是它有textContent属性.

outerText

滚动

  • scrollIntoView() //最常用的方法
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
      2. 3.5.2. outerHTML
      3. 3.5.3. insertAdjacentHTML()
      4. 3.5.4. 内存和性能
    6. 3.6. scrollIntoView()
    7. 3.7. children属性
    8. 3.8. contain()
    9. 3.9. 插入文本
      1. 3.9.1. innerText
      2. 3.9.2. outerText
    10. 3.10. 滚动

访客评论