js学习笔记:DOM扩展

尽管DOM作为API已经十分完善了,但为了实现更多功能,仍会有一些标准或专有的扩展。
对DOM的两个主要的扩展是Selectors API 和HTML5.

这里写图片描述

Selectors API

众多js库中最常用的一项功能就是根据CSS选择符选择与某个模式匹配的DOM元素。实际上,jQuery的核心就是通过CSS选择符查询DOM文档取得元素的引用,从而抛开getElementById()和getElementsByTagName()。

Selectors API 是W3C发起的一个标准,致力于让浏览器原生支持CSS查询。

所有实现这一功能的js库都会写一个基础的CSS解析器,然后再使用已有的DOM方法查询文档并找到匹配的节点。而把这个功能变成原生API后,解析和树查询操作可以在浏览器内部通过编译后的代码来完成,极大地改善了性能。

Selectors API核心是两个方法:querySelector()和querySelectorAll()。
可以通过Document及Element类型的实例调用它们。

querySelector()

接受一个CSS选择符,返回与该模式匹配的第一个元素,如果没有找到则返回null。

//取得body元素
var body = document.querySelector("body");

//取得id为myDiv的元素
var myDiv = document.querySelector("#myDiv");

//取得类为selected的第一个元素
var selected = document.querySelector(".selector");

//取得类为button的第一个图像元素
var img = document.querySelector("img.button");
  • 通过Document类型调用querySelector()方法时,会在文档元素的范围内查找匹配的元素。
  • 通过Element类型调用querySelector()方法时,只会在该元素后代元素的范围内查找匹配的元素。

querySelectorAll()

querySelectorAll()与querySelector()接受地参数一样,都是一个CSS选择符,但返回的是所有匹配的元素而不仅仅是一个元素。这个方法返回的是一个NodeList的实例。

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

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

能调用的querySelectorAll()方法的类型包括Document、DocumentFragment、Element。

//取得某div中所有em元素
var ems = document.getElementById("myDiv").querySelectorAll("em");

//取得类为selected的所有元素
var selecteds = document.querySelectorAll(".selected");

//取得所有p元素中的所有strong元素
var strongs = document.querySelectorAll("p strong");

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

var i,len,strong;
for(i=0,len = strongs.length;i<len;i++){
    strong = strongs[i]; //或者strongs.item(i);
    strong.className = "important";
}

matchesSelector()

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

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

在取得某个元素引用的情况下,使用这个方法能够方便地检测它是否会被querySelector()或querySelectorAll()方法返回。

浏览器支持较差,各浏览器都有实验性的实现,若使用的话最好写一个包装函数。(略)

元素遍历

由于IE与其他浏览器在返回子节点时会因文本节点是否返回而存在差异,因此某规范为DOM元素添加了以下五个属性:

  • childElementCount:返回子元素的个数。(不包括文本节点和注释)
  • firstElementChild:指向第一个子元素。firstChild的元素版。
  • lastElementChild:指向最后一个子元素。lastChild的元素版。
  • previousElementSilbing:指向前一个同辈元素。previousSibling的元素版。
  • nextElementSibling:指向后一个同辈元素。nextSibling的元素版。

支持的浏览器为DOM元素添加了这些属性,利用这些属性不必担心空白文本节点,从而可以更方便的查找DOM元素了。
此时,要遍历某元素的所有子元素,不用考虑浏览器差异了,代码更简洁:

var i,len,child = element.firstElementChild;
for(child != element.lastElementChild){
    processChild(child);
    child = child.nextElementSibling;
}
//有疑问,这样的话不是遍历不到最后一个子元素吗??

HTML5

与类相关的扩充

getElementsByClassName()

可以通过document对象及所有的HTML元素调用该方法。
接收一个参数,即一个包含一或多个类名的字符串,返回带有指定类的所有元素的NodeList。传入多个类名时,类名的先后顺序不重要:

//取得类中包含“username”和“current”的元素,类名的先后顺序不重要。
var allCurrentUsernames = document.getElementsByClassName("current username");

//取得ID为“myDiv”的元素中带有类名“selected”的所有元素:
var selected = document.getElementById("myDiv").getElementsByClassName("selected");

调用这个方法时,只有位于调用元素子树中的元素才会返回。

classList属性

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

HTML5新增了一种操作类名的方式,那就是为所有元素添加classList属性。
这个classList属性是新集合类型DOMTokenList的实例,与其他DOM集合类似,DOMTokenList有一个表示自己包含多少元素的length属性,而要取得每个元素可以使用item()方法,也可以使用方括号语法,此外,这个新类型还定义如下方法:

  • add(value):将指定的字符串值添加到列表中,如果值已经存在,就不添加了。
  • contains(value):表示列表中是否存在给定的值,如果存在则返回true,否则返回false。
  • remove(value):从列表中删除给定的字符串
  • toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。
//删除“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.lengthl;i<len;i++){
    doSomething(div.classList[i]);
}

焦点管理

document.activeElement

这个属性始终都会引用DOM中当前获得了焦点的元素。元素获得焦点的方式有页面加载、用户输入(通常是通过按Tab键)和在代码中调用focus()方法。

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

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

document.hasFocus()

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

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

通过检测文档是否获得了焦点,可以知道用户是不是正在与页面交互。

以上的属性和方法最重要的用途就是提高web应用的无障碍性。无障碍web应用的一个主要标志就是恰当的焦点管理。

HTMLDocument的变化

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

readyState

Document的readyState属性有两个可能的值:
- loading:正在加载文档
- complete:已经加载完文档

使用document.readyState的最佳方式,就是通过它来实现一个指示文档已经加载完成的指示器:

if(document.readyState == "complete"){

}

compatMode

区分渲染页面的模式是标准的还是混杂的:

  • CSS1Compat:标准
  • BackCompat:混杂

引用文档的head元素。

字符集属性

charset

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

document.charset = "UTF-8";

defaultCharset

表示根据默认浏览器及操作系统的设置,当前文档默认的字符集应该是什么。

自定义数据属性

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

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

dataset

添加了自定义属性之后,可以通过元素的dataset属性来访问自定义属性的值。
dataset属性的值是DOMStringMap的一个实例,也就是一个名值对的映射。在这个映射中,每个以data-开头的属性都会有一个对应的属性,只不过此对应属性名没有data-前缀:

var appId = div.dataset.appId;
var myName = div.dataset.myname;

 div.dataset.appId = 23456;
 div.dataset.myname = "michael";

if(div.dataset.myname){

}

如果要给元素添加一些不可见的数据以便进行其他处理,那就要用到自定义数据属性。

插入标记

虽然DOM为操作节点提供了很多控制手段,但在需要给文档插入大量新HTML标记的情况下,通过DOM操作仍然非常麻烦:不仅要创建一系列DOM节点,还要按正确顺序把它们连接起来。
而使用插入标记的技术,直接插入HTML字符串更简单。
以下与标记相关的DOM扩展已经纳入了HTML5规范。

innerHTML

  • 读模式:返回调用元素的所有子节点的HTML标记。(包括元素、注释、文本节点)
  • 写模式:根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点。
<div id="content">
    <p>this is <strong>paragraph</strong> with a list following it.</p>
    <ul>
        <li>item</li>
        <li>item</li>
        <li>item</li>
    </ul>
</div>

对于以上的div元素来说,它的innerHTML属性会返回如下字符串:
< p>this is < strong>paragraph< /strong> with a list following it.< /p>
< ul>
< li>item
< li>item
< li>item
< /ul>
但是不同浏览器返回的文本格式会有所不同。

  • IE和Opera会将所有的标签转换为大写形式
  • Safari、Chrome、FireFox则会原原本本的按照原先文档中的格式返回HTML,包括空格和缩进。

不要指望所有浏览器返回的innerHTML值完全相同。

在写模式下,innerHTML的值会被解析为DOM子树,替换调用元素原来的所有子节点。如果设置的值仅是文本而没有HTML标签,那么结果就是设置纯文本。

div.innerHTML = "hello world";

为innerHTML设置HTML字符串后,浏览器会将这个字符串解析为相应的DOM树。因此设置了innerHTML后,再从中读取HTML字符串,会得到与设置时不一样的结果。原因在于返回的字符串是根据原始HTML字符串创建的DOM数经过序列化之后的结果。

使用innerHTML属性也会有一些限制。比如,在大多数浏览器中,通过innerHTML插入< script>元素并不会执行其中的脚本。
但是大多数浏览器都支持以直观的方式通过innerHTML插入< style>元素。

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

但是并不是所有元素都支持innerHTML属性,不支持的元素有:< col>、< colgroup>、< frameset>、< head>、< html>、< style>、< table>、< tbody>、< thead>、< tfoot>、< tr>

无论什么时候,在使用innerHTML从外部插入HTML之前,都应该先以可靠的方式处理HTML。

outerHTML

  • 读模式:返回调用它的元素及所有子节点的HTML标签
  • 写模式:会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换调用元素。
<div id="content">
    <p>this is <strong>paragraph</strong> with a list following it.</p>
    <ul>
        <li>item</li>
        <li>item</li>
        <li>item</li>
    </ul>
</div>

如果在div元素上调用outerHTML,会返回与上面相同的代码,包括div本身。和innerHTML一样,也会因浏览器不同返回结果有差异。

使用outerHTML属性以下面这种方式设置值:

div.outerHTML = "<p>this is paragraph</p>";

结果就是新创建的< p>元素会取代DOM树中的div元素。

insertAdjacentHTML()

接收两个参数:插入位置和要插入的HTML文本。
第一个参数必须是下列值之一:

  • “beforebegin”:在当前元素之前插入一个紧邻的同辈元素
  • “afterbegin”:插入元素内部的第一个子节点之前。
  • “beforeend”:插入元素内部的最后一个子节点之后。
  • “afterend”:在当前元素之后插入一个紧邻的同辈元素。

这些值都必须是小写形式。第二个参数是一个HTML字符串(与innerHTML和outerHTML一样)。

<!-- beforebegin --> 
<p> 
<!-- afterbegin --> 
foo 
<!-- beforeend --> 
</p> 
<!-- afterend -->
//作为前一个同辈元素插入
element.insertAdjacentHTML("beforebegin","<p>this is paragraph</p>");

//作为第一个子元素插入
element.insertAdjacentHTML("afterbegin","<p>this is paragraph</p>");

//作为最后一个子元素插入
element.insertAdjacentHTML("beforeend","<p>this is paragraph</p>");

//作为后一个同辈元素插入
element.insertAdjacentHTML("afterend","<p>this is paragraph</p>");

内存与性能问题

使用上面介绍的方法替换子节点可能会导致浏览器的内存占用问题。
在删除带有事件处理程序或引用了其他js对象的子树时,就有可能导致内存占用问题。
加入某个元素有一个事件处理程序,在使用前面某个属性将元素从文档树中删除后,元素与事件处理程序之间的绑定关系在内存中并没有一并删除。如果这种情况频繁出现,页面占用的内存数量就会明显增加。
因此,在使用innerHTML、outerHTML和insertAdjacentHTML()之前,最好先手工删除要被替换的元素的所有事件处理程序和js对象属性。

另外,使用innerHTML效率明显高,这是由于使用属性时创建的解释器是在浏览器级别的代码基础上运行的。但是也不能无限制地使用innerHTML(如在循环中使用,每次循环都innerHTML)。
最佳实践应该是单独构建字符串,然后再一次性的将结果字符串赋值给innerHTML。

scrollIntoView()

如何滚动页面也是DOM规范没有解决的一个问题。HTML5最终选择了scrollIntoView()作为标准方法。

scrollIntoView()可以在所有HTML元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中。

  • 如果给这个方法传入true作为参数,或者不传入任何参数,那么窗口滚动之后会让调用元素的顶部与视口顶部尽可能平齐。
  • 如果传入false作为参数,调用元素会尽可能全部出现在视口中,(可能的话,调用元素的底部会与视口底部平齐)。
//让元素可见
document.forms[0].scrollIntoView();

当页面发生变化时,一般用这个方法来吸引用户的注意力。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值