5.客户端JavaScript笔记:DOM扩展(HTML5)

本系列内容由ZouStrong整理收录

整理自《JavaScript权威指南(第六版)》,《JavaScript高级程序设计(第三版)》

DOM经非常完善了,但为了实现更多的功能,仍然会有一些标准或专有的扩展

一. 选择器API(Selector API)

很多JavaScript库都可以根据CSS选择器来直接选取元素,但本质上还是使用现有的DOM方法查询文档并找到匹配的节点,并且库开发人员在不知疲倦地改进这一过程的性能,但到头来都只能通过运行JavaScript代码来完成查询操作

而把这个功能变成原生API之后,解析和树查询操作可以在浏览器内部通过编译后的代码来完成,极大地改善了性能,这就是Selectors API

1. querySelector()方法(IE6、7不支持)

该方法接受一个CSS选择器,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null,如果传入了不被支持的选择器,则会抛出错误

可以通过Document及Element类型的实例调用它

var body = document.querySelector("body"); 
var div = document.querySelector("#div"); 
var img = div.querySelector("img.button");

2. querySelectorAll()方法(IE6、7不支持)

该方法接受一个CSS选择器,返回与该模式匹配的所有元素(NodeList的实例),如果没有找到匹配的元素,则返回空的NodeList对象,如果传入了不被支持的选择器,则会抛出错误

可以通过Document及Element类型的实例调用它

这里返回的NodeList,是一组元素的快照,而非不断对文档进行搜索的动态查询。这样实现可以避免使用NodeList对象通常会引起的大多数性能问题

var sel = document.querySelectorAll(".s"); 

要取得返回的NodeList中的每一个元素,可以使用方括号语法或item()方法

3.matchesSelector()方法

该方法接受一个CSS选择器,如果调用元素与该选择器匹配,返回true;否则,返回false

只能通过Element类型的实例调用它

if (document.body.matchesSelector(".page")){ 

} 

截至现在(2015/7/20 )还没有浏览器支持该方法

但是IE9+通过msMatchesSelector()支持该方法,Firefox 3.6+通过mozMatchesSelector()支持该方法,Safari和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, ".page1")){ 

} 

二. 元素遍历(Element Traversal)

对于元素间的空格,IE9及之前版本不会返回文本节点,而其他所有浏览器都会返回文本节点。这就导致了在使用childNodes和firstChild等属性时的行为不一致

元素遍历规范新定义了一组属性(IE6、7、8不支持)

  • childElementCount:返回子元素(仅包含元素节点)的个数
  • firstElementChild:指向第一个子元素(firstChild的元素版)
  • lastElementChild:指向最后一个子元素(lastChild的元素版)
  • previousElementSibling:指向前一个同辈元素(previousSibling的元素版)
  • nextElementSibling:指向后一个同辈元素(nextSibling的元素版)

三. HTML5中的DOM扩展

1. 与类名相关的扩展

HTML5新增了很多API,致力于简化CSS类的用法

getElementsByClassName()方法(IE6、7、8不支持)

该方法接收一个包含一个或者多个类名(顺序无关)的字符串,返回带有指定类名的元素的NodeList(与使用getElementsByTagName()
以及其他返回 NodeList 的 DOM 方法都具有同样的性能问题,这一点上选择器API就做的很好)

可以通过Document及Element类型的实例调用它

classList属性(IE6、7、8、9不支持)

在操作类名时,需要通过className属性添加、删除和替换类名。因为className中是一个字符串,所以即使只修改字符串一部分,也必须每次都设置整个字符串的值

HTML5为所有元素添加了classList属性,是一个class值的集合,可以使用length属性查看class值的数量,使用item()方法或方括号语法访问指定位置的class值

此外,还定义如下方法,极大的减少了类名操作的复杂性(就好像在使用JavaScript库一样方便)

  • add():添加指定的class值(如果已经存在,则不再添加)
  • remove():从列表中删除给定的class值
  • contains(value):表示列表中是否存在给定的值,如果存在则返回true,否则返回false
  • toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它

div.classList.remove("hide"); 
div.classList.add("hide"); 
div.classList.toggle("hide"); 

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

2. 焦点管理

HTML5也添加了辅助管理DOM焦点的功能

document.activeElement属性(所有浏览器都支持)

这个属性始终会引用DOM中当前获得了焦点的元素,元素获得焦点的方式有

  • 页面加载
  • 用户输入(通常是通过按Tab键)
  • 在代码中调用focus()方法

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

默认情况下,文档加载期间

document.activeElement===null

而文档刚刚加载完成时

document.activeElement===document.body

document.hasFocus()方法(所有浏览器都支持)

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

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

3. HTMLDocument的变化

HTML5扩展了HTMLDocument,增加了新的功能

readyState属性(所有浏览器都支持)

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

readyState属性有两个可能的值

  • loading:正在加载文档
  • complete:已经加载完文档

div.onclick = function(){
	if(document.readyState==="complete"){
	}
};

在这个属性得到广泛支持之前,要实现这样一个指示器,必须借助onload事件处理程序设置一个标签,表明文档已经加载完毕

window.onload = function(){
	div.onclick = function(){
	}		
}

compatMode属性(所有浏览器都支持)

IE给document添加了compatMode属性,告诉开发人员浏览器采用了哪种渲染模式,最终,HTML5也把这个属性纳入标准

在标准模式下

document.compatMode==="CSS1Compat"

而在混杂模式下

document.compatMode==="BackCompat"

head属性(IE6、7、8不支持)

document.body属性可以取得body元素,因此HTML5新增document.head属性,引用文档的head元素

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

charset、defaultCharset属性(所有浏览器都支持)

HTML5新增了几个与文档字符集有关的属性

charset属性表示文档中实际使用的字符集,也可以用来指定新字符集

alert(document.charset); 
document.charset = "UTF-8";

defaultCharset属性表示根据浏览器及操作系统的设置,当前文档默认的字符集应该是什么(它是只读的)

alert(document.defaultCharset); 

dataset属性(IE10及以下不支持)

HTML5规定可以为元素添加非标准的属性,但要添加前缀data-,目的是为元素提供与渲染无关的信息,或者提供语义信息

<div data-age="26 data-name="strong"></div>

添加了自定义属性之后,可以通过元素的dataset属性来访问自定义属性的值

dataset属性返回所有自定义属性组成的对象,属性值就是自定义属性名(没有data-前缀),属性值就是自定义属性值

div.dataset.age;
div.dataset.name="strongs"; 

4. 插入标记

之前讲到的DOM操作(appendChild()、insertBefore()等)都是比较精细的手段,但是有的时候却不需要那么精细,以下与插入标记相关的DOM扩展已经纳入了HTML5规范

innerHTML属性(所有浏览器都支持)

在读模式下,innerHTML属性返回与调用元素的所有子节点(包括元素、注释和文本节点)对应的HTML标记。在写模式下,innerHTML会根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点

并不是所有元素都支持innerHTML属性,不支持innerHTML的元素有

<col>、<colgroup>、<frameset>、<head>
<html>、<style>、<table>、<tbody>
<thead>、<tfoot>和<tr>

此外,在IE8及更早版本中,元素也没有innerHTML属性</p>

outerHTML属性(所有浏览器都支持)

在读模式下,outerHTML返回调用它的元素及所有子节点的HTML标签。在写模式下,outerHTML会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换调用元素

insertAdjacentHTML()方法(所有浏览器都支持)

该方法接收两个参数,第一个参数表示插入位置,必须是下列值之一

  • "beforebegin",在当前元素之前插入一个紧邻的同辈元素
  • "afterbegin",在当前元素之下插入一个新的子元素或在第一个子元素之前再插入新的子元素
  • "beforeend",在当前元素之下插入一个新的子元素或在最后一个子元素之后再插入新的子元素
  • "afterend",在当前元素之后插入一个紧邻的同辈元素

第二个参数表示插入的值

内存与性能问题

使用本节介绍的方法替换子节点可能会导致浏览器的内存占用问题,尤其是在IE中

假设某个元素有一个事件处理程序(或者引用了一个JavaScript对象作为属性),在使用前述某个属性将该元素从文档树中删除后,元素与事件处理程序(或JavaScript对象)之间的绑定关系在内存中并没有一并删除。如果这种情况频繁出现,页面占用的内存数量就会明显增加

因此,在使用innerHTML、outerHTML属性和insertAdjacentHTML()方法时,最好先手工删除要被替换的元素的所有事件处理程序和JavaScript对象属性

不过,使用这几个属性——特别是使用innerHTML,仍然还是可以为我们提供很多便利的。一般来说,在插入大量新HTML标记时,使用innerHTML属性与通过多次DOM操作先创建节点再指定它们之间的关系相比,效率要高得多

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

for (var i=0, len=values.length; i < len; i++){ 
	ul.innerHTML += "<li>" + values[i] + "</li>"; 
	//要避免这种频繁操作!!
} 

最好的做法是单独构建字符串,然后再一次性地将结果字符串赋值给innerHTML

var itemsHtml = "";
for (var i=0, len=values.length; i < len; i++){ 
	itemsHtml += "<li>" + values[i] + "</li>"; 
} 
ul.innerHTML = itemsHtml;

这个例子的效率要高得多,因为它只对innerHTML执行了一次赋值操作

5. scrollIntoView()方法(所有浏览器都支持)

scrollIntoView()可以在所有HTML元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中。如果给这个方法传入true作为参数,或者不传入任何参数,那么窗口滚动之后会让调用元素的顶部与视口顶部尽可能平齐。如果传入false作为参数,调用元素会尽可能全部出现在视口中(可能的话,调用元素的底部会与视口顶部平齐)

//让元素可见
document.forms[0].scrollIntoView();

四. 专有扩展

很多专有扩展都被HTML5纳入了标准之中,但仍然还有大量专有的DOM扩展没有成为标准(或许以后会成为标准)

1. 文档模式(IE专有)

强制IE以不同版本的模式来渲染

<meta http-equiv="X-UA-Compatible" content="IE=IE版本">

其中,IE版本值可能为

  • Edge:始终以最新的文档模式来渲染页面(忽略文档类型声明。对于IE8,始终保持以IE8标准模式渲染页面。对于IE9,则以IE9标准模式渲染页面.....)
  • EmulateIE+数字(5-11):如果有文档类型声明,则以数字指定的IE版本的标准模式渲染页面,否则将文档模式设置为IE5。
  • 数字(5-11):强制以数字指定的IE版本的标准模式渲染页面,忽略文档类型声明。

2. children属性(支持不太完美)

children属性是childNodes属性的进化版,它只会返回元素的子元素,不会有那些莫名其妙的节点混进来

IE8及以下浏览器会返回注释节点

3. contains()方法(都支持)

调用contains()方法的应该是祖先节点,也就是搜索开始的节点,这个方法接收一个参数,即要检测的后代节点。如果被检测的节点是后代节点,该方法返回true;否则,返回false

4. 插入文本

IE原来专有的插入标记的属性innerHTML和outerHTML已经被HTML5纳入规范。

但另外两个插入文本的专有属性并没有被纳入规范

innerText属性(火狐不支持)

通过innertText 属性可以操作元素中包含的所有文本内容,包括子文档树中的文本。在通过innerText 读取值时,它会按照由浅入深的顺序,将子文档树中的所有文本拼接起来。在通过innerText写入值时,结果会删除元素的所有子节点,插入包含相应文本值的文本节点

outerText属性(火狐不支持)

除了作用范围扩大到了包含调用它的节点之外,outerText与innerText基本上没有多大区别

由于这个属性会导致调用它的元素不存在,因此并不常用

5. 滚动

HTML5在将scrollIntoView()纳入规范之后,仍然还有其他几个专有方法可以在不同的浏览器中使用

可以在HTMLElement类型上调用它

  • scrollIntoViewIfNeeded
  • scrollByPages(pageCount)

你可能感兴趣的:(JavaScript)