《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中添加的非标准扩展,有些写进标准了,有些没有写进标准成为了自己浏览器专用的内容。这里不太想看,还是略去好了。