《JavaScript高级程序设计》第十一章,DOM扩展。
标准的DOM API有时候满足不了大家的需求,于是便衍生了一些扩展API,最主要的两个扩展:Selectors API、HTML5。
选择符API
querySelector()
接收CSS选择符,返回匹配的第一个元素,无则返回null。当使用Document调用此方法时,会在整个文档元素范围内查找;当使用Element调用此方法时,只会在该元素后代元素范围内查找。
querySelectorAll()
接收CSS选择符,返回匹配的所有元素(一个NodeList),无则返回空的NodeList。其它与上一致。访问NodeList里的元素有两种语法:list[i]、list.item(i)。
matchesSelector()
用Element调用此方法,接收CSS选择符,如果调用元素与该选择符匹配则返回true,否则返回false。不过目前这个方法在各个浏览器上暂未统一,比如我用Chrome发现它的该方法是webkitMatchesSelector()。
元素遍历
问题源于,对于元素之间的空格,IE9之前的版本不会返回文本节点,而其它所有浏览器都会将将其作为一个文本节点。这样的话,用诸如childNodes、firstChild的属性时候,浏览器行为不一致。因此为了在DOM规范不变的情况下弥补差异,Element Traversal API为DOM元素添加了5个新属性。
childElementCount:子元素个数(不含文本节点和注释)firstElementChild:第一个元素(firstChild的元素版)lastElementChild:最后一个元素(lastChild的元素版)previousElementSibling:前一个兄弟元素(previousSibling的元素版)nextElementSibling:后一个兄弟元素(nextSibling的元素版)
HTML5
为什么会在这里突然谈到HTML5呢?因为之前的HTML版本里对JavaScript的接口描述很少,与JavaScript相关的都放到DOM规范里去了;而HTML5规范里新增了很多标记,也围绕这些标记定义了很多JavaScript API。有些API与DOM重叠,因此这里介绍一下和DOM相关的内容。
类相关
HTML4之后class属性用得越来越多,为了方便,HTML5新增了一些和类相关的API。
getElementByClassName()
之前只有byId和byTagName。此方法接收包含一个或多个类名的字符串(用空格隔开),返回调用者后代范围内匹配到的NodeList,类名的先后顺序不重要。如果是多个类名,则是AND的关系而不是OR的关系。1
2
3
4
5
6// AND
let classAandB = document.getElementByClassName("a b");
let classAandB = document.querySelectorAll(".a.b");
// OR
let classAorB = document.querySelectorAll(".a, .b");classList
之前是用className字符串表示所有的类名的,对类的增删查改就是字符串操作,比较麻烦。此属性属于新的DOMTokenList类型,有如下的方法:add(value)remove(value)contains(value)toggle(value):切换,如果列表中存在该类则删除它;如果列表中不存在该类则添加它
焦点相关
HTML5添加了辅助管理DOM焦点的功能。
document.activeElement
此属性始终为DOM中当前获得了焦点的元素。有几种获得焦点的方式:页面加载、用户输入(通常通过按Tab)、代码中调用focus()。默认情况下,文档加载期间该值为null,加载完成时为document.body;element.focus()
聚焦某个元素。document.hasFocus()
判断文档是否获得了焦点。
HTMLDocument相关
HTML5扩展了HTMLDocument,增加了新功能。以下内容均可通过document.xxx访问。
readyState
之前想判断文档是否加载完成的话,得借助onload事件处理程序来设置flag,现在可以直接用这个属性了。此属性有两个取值:"loading"表示正在加载文档,"complete"表示已加载完文档。compatMode
此属性用来区分页面的兼容模式,有两个取值:"CSS1Compat"表示标准模式,"BackCompat"表示混杂模式。head
之前只有document.body,现在添加一下document.head,来引用文档里的<head>元素。
字符集相关
HTML5新增了几个与文档字符集相关的属性。
charset
表示文档使用的字符集,可读可写,默认值为"UTF-16",修改方式有几种:通过<meta>元素、响应头部、直接设置charset属性。defaultCharset
表示根据默认浏览器及操作系统的设置得到的字符集。
自定义数据属性相关
HTML5规定可以给元素添加自定义属性,使用方法如下。
1 | <div id="myDiv" data-appId="12345" data-appName="helloworld"></div> |
1 | let div = document.getElementById("myDiv"); |
插入标记相关
虽然DOM为操作节点提供了很多API,但插入比较麻烦,一是要创建一系列节点,二是要小心地按照顺序插入。相对而言,插入标记的话会比较简单方便。
innerHTML
读模式下,返回调用元素的所有后代元素(不包括它本身)。要注意的是不同浏览器返回的文本格式会有所不同,譬如IE和Opera会把标签转为大写,Safari、Chrome和Firefox则返回原本的格式。
写模式下,要注意的是innerHTML的值会被解析为DOM子树,中间会经历字符串的序列化(比如"会变成"),因此设置后获取到的字符串可能和设置的字符串是不一样的。
这个属性也有一些限制:对于IE,如果通过innerHTML插入的字符串开头就是一个“无作用域的元素”(如<script>、<style>),那这个字符串会在解析之前被删除;对于大多数浏览器,通过innerHTML插入<script>元素不起作用,只有IE在一定条件下(需指定defer属性且<script>元素要位于有作用域元素之后)可以。有几种解决办法:
1 | div.innerHTML = "_<script defer>alert('hi');<\/script>" |
除此之外,并不是所有元素都支持innerHTML属性,如<col>、<colgroup>、<frameset>、<head>、<html>、<style>、<table>、<tbody>、<thead>、<tfoot>、<tr>。
通过innerHTML插入的内容需要保证无害,IE8为此提供了一个方法window.toStaticHTML(),用来将传入的HTML字符串转为安全无害版本,即删除所有脚本节点和事件处理程序属性。
1 | let text = "<a href=\"#\" onclick=\"alert('hi')\">Click Me</a>"; |
outerHTML
读模式下,返回调用它的元素及其所有子节点的HTML标签。
写模式下,会创建新的DOM子树,并用这个DOM子树完全替换掉调用元素。insertAdjacentHTML()
接收两个参数:插入位置和需要插入的HTML文本。
前者有以下选择:"beforebegin":在当前元素之前插入一个紧邻的同辈元素"afterbegin":在当前元素下插入一个新的子元素/在第一个子元素之前插入新的子元素"beforeend":在当前元素下插入一个新的子元素/在最后一个子元素之后插入新的子元素"afterend":在当前元素之后插入一个紧邻的同辈元素
* 内存与性能问题
性能问题一,用以上方法会删除一些带有事件处理程序或引用其他JavaScript对象子树,但删除该元素的同时并不会删除元素与事件处理程序或JavaScript对象之间的绑定关系。因此在使用以上方法时候最好手动鉴别删除一下。
性能问题二,使用innerHTML或outerHTML时,会创建一个HTML解析器,它是浏览器级别的代码(通常用C++写的)基础上运行的,因此比执行JavaScript快。带来的好处是,使用这些方法比通过DOM创建节点再指定它们之间的关系来得快;缺点则是,创建和销毁HTML解析器有性能损失。因此最好能限制一下调用以上方法的次数。
scrollIntoView()
此方法可以在所有HTML元素上调用,通过滚动浏览器窗口或某个容器元素,使得调用元素出现在视口。当传入true或不传参数时,会让调用元素顶部与视口顶部尽量平齐;当传入false时,会让调用元素尽量全部出现在视口(可能的话会让底部平齐)。
专用扩展
也就是不同浏览器自己往DOM中添加的非标准扩展,有些写进标准了,有些没有写进标准成为了自己浏览器专用的内容。这里不太想看,还是略去好了。