文章目录
11 DOM扩展
内容
- 理解 Selectors API
- 使用 HTML5 DOM 扩展
- 了解专有的 DOM 扩展
11.1 选择符API
Selectors API( www.w3.org/TR/selectors-api/)是由 W3C 发起制定的一个标准,致力于让浏览器原生支持 CSS 查询。所有实现这一功能的 JavaScript 库都会写一个基础的 CSS 解析器,然后再使用已有的DOM 方法查询文档并找到匹配的节点。核心方法
- querySelector
- querySelectorAll
11.1.1 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()方法时,只会在该元素后代元素的范围内查找匹配的元素。
11.1.2 querySelector()方法
querySelector和querySelectorAll之间传递的参数类型,但是querySelectorAll返回的是包含属性和方法的NodeList实例。
//取得某<div>中的所有<em>元素(类似于 getElementsByTagName("em"))
var ems = document.getElementById("myDiv").querySelectorAll("em");
//取得类为"selected"的所有元素
var selecteds = document.querySelectorAll(".selected");
//取得所有<p>元素中的所有<strong>元素
var strongs = document.querySelectorAll("p strong");
var i,len,strong;
for(i=0,len=strong.length;i<len;i++){
strong = strongs[i];//或者strongs.item(i)
strong.className = "important"
}
11.1.3matchesSelector()方法
接收一个参数,即 CSS 选择符,如果调用元素与该选择符匹配,返回 true;否则,返回 false。
if (document.body.matchesSelector("body.page1")){
//true
}
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")){
//执行操作
}
11.2 元素遍历
ElementTraversal规范为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;
}
13.3 HTML5
对于传统 HTML 而言, HTML5 是一个叛逆。所有之前的版本对 JavaScript 接口的描述都不过三言两语,主要篇幅都用于定义标记,与 JavaScript 相关的内容一概交由 DOM 规范去定义。
而 HTML5 规范则围绕如何使用新增标记定义了大量 JavaScript API。其中一些 API 与 DOM 重叠,定义了浏览器应该支持的 DOM 扩展。
11.3.1 与类有关的扩充
- getElementsByClassName()方法
HTML5 添加的 getElementsByClassName()方法是最受人欢迎的一个方法,可以通过 document对象及所有 HTML 元素调用该方法。这个方法最早出现在 JavaScript 库中,是通过既有的 DOM 功能实现的,而原生的实现具有极大的性能优势
//取得所有类中包含"username"和"current"的元素,类名的先后顺序无所谓
var allCurrentUsernames = document.getElementsByClassName("username current");
//取得 ID 为"myDiv"的元素中带有类名"selected"的所有元素
var selected = document.getElementById("myDiv").getElementsByClassName("selected");
- classList 属性
在操作类名时,需要通过 className 属性添加、删除和替换类名。因为 className 中是一个字符串,所以即使只修改字符串一部分,也必须每次都设置整个字符串的值。
<div class="bd user disabled">...</div>
//首先,取得类名字符串并拆分成数组
var classNames = div.className.split("/\s+/");
//找到要删的类名
var pos = -1,
i,
len;
for(i = 0;len;className.length;i<len;i++){
if(className[i]=="user"){
pos = i;
break;
}
}
//删除类名
classNames.splice(i,1);
//把剩下的类名拼成字符串并重新设置
div.className = classNames.join(" ");
HTML5 新增了一种操作类名的方式,可以让操作更简单也更安全,那就是为所有元素添加classList 属性。这个 classList 属性是新集合类型 DOMTokenList 的实例:
- add(value):将给定的字符串值添加到列表中。如果值已经存在,就不添加了。
- contains(value):表示列表中是否存在给定的值,如果存在则返回 true,否则返回false。
- remove(value):从列表中删除给定的字符串。
- toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。
这样,前面那么多行代码用下面这一行代码就可以代替了:
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]);
}
11.3.2 焦点管理
HTML5 也添加了辅助管理 DOM 焦点的功能。首先就是 document.activeElement 属性,这个
属性始终会引用 DOM 中当前获得了焦点的元素。元素获得焦点的方式有页面加载、用户输入(通常是通过按 Tab 键)和在代码中调用 focus()方法
var button = document.getElementById("myButton");
button.focus();
alert(docuemnt.activeElement===button); //true
//文档加载完成 document.activeElement保存的是document.body元素的引用
//文档加载期间 document。activeElement值为null
增加了document.hasFocus,用于确定文档是否获得焦点
var button = document.getElementById("myButton");
button.focus();
alert(document.hasFocus()); //true
11.3.3HTMLDocument变化
HTML5 扩展了 HTMLDocument,增加了新的功能。与 HTML5 中新增的其他 DOM 扩展类似,这些变化同样基于那些已经得到很多浏览器完美支持的专有扩展。
- readyState属性
IE4 最早为 document 对象引入了 readyState 属性。然后,其他浏览器也都陆续添加这个属性,最终 HTML5 把这个属性纳入了标准当中。
- loading,正在加载文档;
- complete,已经加载完文档
if(document.readyState=="complete"){
//执行操作
}
- 兼容模式
IE6开始区分页面渲染模式,标准模式/混杂模式 compatMode
if (document.compatMode == "CSS1Compat"){
alert("Standards mode");
} else {
alert("Quirks mode");
}
- head属性
HTML5 新增了 document.head 属性,引用文档的/元素。
var head = document.head || document.getElementsByTagName("head")[0];
11.3.4 字符集属性
alert(document.charset); //UTF-16
document.charset = "UTF-8";
另一个属性是 defaultCharset,表示根据默认浏览器及操作系统的设置,当前文档默认的字符集应该是什么
if(document.charset!=document.defaultCharset){
alert("custom charset set being used")
}
11.3.5 自定义数据属性
HTML5 规定可以为元素添加非标准的属性,但要添加前缀 data-,目的是为元素提供与渲染无关的信息,或者提供语义信息。
<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>
添加了自定义属性之后,可以通过元素的 dataset 属性来访问自定义属性的值。 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";
//有没有"myname"值呢?
if (div.dataset.myname){
alert("Hello,"+div.dataset.myname);
}
11.3.6 插入标记
- innerHTML
在读模式下, innerHTML 属性返回与调用元素的所有子节点(包括元素、注释和文本节点)对应的 HTML 标记。在写模式下, innerHTML 会根据指定的值创建新的 DOM 树,然后用这个 DOM 树完全替换调用元素原先的所有子节点。
<div id="content">
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
上面div的innerHTML返回如下:
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
- outerHTML属性
在读模式下, outerHTML 返回调用它的元素及所有子节点的 HTML 标签。 在写模式下, outerHTML会根据指定的 HTML 字符串创建新的 DOM 子树,然后用这个 DOM 子树完全替换调用元素
div.outerHTML = "<p>This is a paragraph.</p>";
//这行代码完成的操作与下面这些 DOM 脚本代码一样:
var p = document.createElement("p");
p.appendChild(document.createTextNode("This is a paragraph."));
div.parentNode.replaceChild(p, div);
- insertAdjacentHTML()方法
插入标记的最后一个新增方式是 insertAdjacentHTML()方法。这个方法最早也是在IE中出现的,它接收两个参数:插入位置和要插入的 HTML 文本。
- “beforebegin”,在当前元素之前插入一个紧邻的同辈元素;
- “afterbegin”, 在当前元素之下插入一个新的子元素或在第一个子元素之前再插入新的子元素;
- “beforeend”, 在当前元素之下插入一个新的子元素或在最后一个子元素之后再插入新的子元素;
- “afterend”,在当前元素之后插入一个紧邻的同辈元素。
//作为前一个同辈元素插入
element.insertAdjacentHTML("beforebegin", "<p>Hello world!</p>");
//作为第一个子元素插入
element.insertAdjacentHTML("afterbegin", "<p>Hello world!</p>");
//作为最后一个子元素插入
element.insertAdjacentHTML("beforeend", "<p>Hello world!</p>");
//作为后一个同辈元素插入
element.insertAdjacentHTML("afterend", "<p>Hello world!</p>");
- 内存与性能问题
在删除带有事件处理程序或引用了其他 JavaScript 对象子树时,就有可能导致内存占用问题。假设某个元素有一个事件处理程序(或者引用了一个 JavaScript 对象作为属性),在使用前述某个属性将该元素从文档树中删除后,元素与事件处理程序(或 JavaScript 对象)之间的绑定关系在内存中并没有一并删除。如果这种情况频繁出现,页面占用的内存数量就会明显增加。因此,在使用 innerHTML、outerHTML 属性和 insertAdjacentHTML()方法时,最好先手工删除要被替换的元素的所有事件处理程序和 JavaScript 对象属性。
效率低的方法:
for(var i=0,len = values.length;i<len;i++){
ul.innerHTML +="<li>"+values[i]+"</li>";//避免此种操作
}
改进效率后的办法:
var itemsHTML = "";
for(var i=0,len = values.length;i<len;i++){
itemHTML +="<li>"+values[i]+"</li>";
}
ul.innerHTML = itemsHTML;
11.3.7 scrollIntoView
scrollIntoView()可以在所有 HTML 元素上调用,通过滚动浏览器窗口或某个容器元素,调用
元素就可以出现在视口中。如果给这个方法传入 true 作为参数,或者不传入任何参数,那么窗口滚动之后会让调用元素的顶部与视口顶部尽可能平齐。如果传入 false 作为参数,调用元素会尽可能全部出现在视口中.
//让元素可见
document.forms[0].scrollIntoView();
11.4 专有扩展
11.4.1 文档标准
IE8 引入了一个新的概念叫“文档模式”( document mode)。
- IE5:以混杂模式渲染页面( IE5 的默认模式就是混杂模式)。 IE8 及更高版本中的新功能都无法使用。
- IE7:以 IE7 标准模式渲染页面。 IE8 及更高版本中的新功能都无法使用。
- IE8:以 IE8 标准模式渲染页面。 IE8 中的新功能都可以使用,因此可以使用 Selectors API、更多CSS2 级选择符和某些 CSS3 功能,还有一些 HTML5 的功能。不过 IE9 中的新功能无法使用。
- IE9:以 IE9 标准模式渲染页面。 IE9 中的新功能都可以使用,比如 ECMAScript 5、完整的 CSS3以及更多 HTML5 功能。这个文档模式是最高级的模式。
11.4.2 children属性
这个属性是 HTMLCollection 的实例,只包含元素中同样还是元素的子节点。
11.4.3 contains方法
IE 为此率先引入了 contains()
方法,以便不通过在 DOM 文档树中查找即可获得这个信息。调用 contains()方法的应该是祖先节点,也就是搜索开始的节点,这个方法接收一个参数,即要检测的后代节点。
alert(document.documentElement.contains(document.body)); //true
使用 DOM Level 3 compareDocumentPosition()也能够确定节点间的关系。
function contains(refNode,otherNode){
if(typeof refNode.contains == "function"&&(!client.engine.webkit||client.engine.webkit>=522)){
return ref.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{
ndoe = node.parentNode;
}
}while(node!==null);
return false;
}
}
11.4.4 插入文本
- innerText属性
通过 innertText 属性可以操作元素中包含的所有文本内容,包括子文档树中的文本。在通过innerText 读取值时,它会按照由浅入深的顺序,将子文档树中的所有文本拼接起来。在通过innerText 写入值时,结果会删除元素的所有子节点,插入包含相应文本值的文本节点。
<div id="content">
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
对于这个例子中的<div>元素而言,其 innerText 属性会返回下列字符串:
This is a paragraph with a list following it.
Item 1
Item 2
Item 3
innerText特性检查
function getInnerText(element){
return (typeof element.textContent == "string")?element.textContent:element.innerText;
}
function setInnerText(element)
if(typeof element.textContent == "string"){
element.textContent = text;
}else{
element.innerText = text;
}
实际上, innerText 与 textContent 返回的内容并不完全一样。比如,innerText 会忽略行内的样式和脚本,而 textContent 则会像返回其他文本一样返回行内的样式和脚本代码。避免跨浏览器兼容问题的最佳途径,就是从不包含行内样式或行内脚本的 DOM 子树副本或 DOM 片段中读取文本。
- outText属性
在读取文本值时, outerText 与 innerText 的结果完全一样。但在写模式下, outerText 就完全不同了: outerText 不只是替换调用它的元素的子节点,而是会替换整个元素
div.outerText = "Hello world!";
//这行代码实际上相当于如下两行代码:
var text = document.createTextNode("Hello world!");
div.parentNode.replaceChild(text, div);
11.4.5 滚动
- scrollIntoViewIfNeeded(alignCenter):只在当前元素在视口中不可见的情况下,才滚动浏览器窗口或容器元素,最终让它可见。如果当前元素在视口中可见,这个方法什么也不做。如果将可选的 alignCenter 参数设置为 true,则表示尽量将元素显示在视口中部(垂直方向)。Safari 和 Chrome 实现了这个方法。
- scrollByLines(lineCount):将元素的内容滚动指定的行高, lineCount 值可以是正值,也可以是负值。 Safari 和 Chrome 实现了这个方法。
- scrollByPages(pageCount):将元素的内容滚动指定的页面高度,具体高度由元素的高度决定。 Safari 和 Chrome 实现了这个方法。
//将页面主体滚动 5 行
document.body.scrollByLines(5);
//在当前元素不可见的时候,让它进入浏览器的视口
document.images[0].scrollIntoViewIfNeeded();
//将页面主体往回滚动 1 页
document.body.scrollByPages(-1);
11.5 小结
本章介绍的三个这方面的规范如下。
- Selectors API,定义了两个方法,让开发人员能够基于 CSS 选择符从 DOM 中取得元素,这两个
方法是 querySelector()和 querySelectorAll()。 - Element Traversal,为 DOM 元素定义了额外的属性,让开发人员能够更方便地从一个元素跳到
另一个元素。之所以会出现这个扩展,是因为浏览器处理 DOM 元素间空白符的方式不一样。 - HTML5,为标准的 DOM 定义了很多扩展功能。其中包括在 innerHTML 属性这样的事实标准基
础上提供的标准定义,以及为管理焦点、设置字符集、滚动页面而规定的扩展 API。