JavaScript学习笔记(13):DOM扩展

Selectors API

Selectors API是W3C推荐标准,规定了浏览器原生支持的CSS查询API,即根据CSS选择符的模式匹配DOM元素

querySelector

querySelector()方法接收CSS选择符参数,返回匹配该模式的第一个后代元素,如果没有匹配项则返回null。

在Document上使用querySelector()方法时,会从文档元素开始搜索;在Element上使用querySelector()方法时,则只会从当前元素的后代中查询。

如果选择符有语法错误或碰到不支持的选择符,则querySelector()方法会抛出错误。

querySelectorAll

querySelectorAll()方法跟querySelector()一样,也接收一个用于查询的参数,但它会返回所有匹配的节点,而不止一个。这个方法返回的是一个NodeList的静态实例。因为如果返回动态的,将导致极大的性能开销。

如果没有匹配项,则返回空的NodeList实例。返回的NodeList对象也可以通过for-of循环、item()方法或中括号语法取得个别元素。

如果选择符有语法错误或碰到不支持的选择符也会抛出错误。

matches

在元素上调用matches()方法并接收一个CSS选择符作为参数,如果元素匹配则该选择符,该方法返回true,否则返回false。

使用这个方法可以方便地检测某个元素会不会被querySelector()querySelectorAll()方法返回。

元素遍历

IE9之前的版本不会把元素间的空格当成空白节点,而其他浏览器则会。这样就导致了childNodes和firstChild等与子节点相关的属性的差异。

为了方便地遍历元素,规范为DOM元素添加了以下属性:

  • childElementCount:返回子元素数量。
  • firstElementChild:指向第一个Element类型的子元素。
  • lastElementChild:指向最后一个Element类型的子元素。
  • previousElementSibling:指向前一个Element类型的同胞元素。
  • nextElementSibling:指向后一个Element类型的同胞元素。

这些属性都只包含元素节点,而不包含文本节点和注释节点,这样就可以方便地遍历元素了。

IE9及以上版本,以及所有现代浏览器都支持Element Traversal属性。

HTML5

在HTML5标准中新增了一些和DOM有关的内容。

CSS类扩展

getElementsByClassName方法

getElementsByClassName()是HTML5新增的一个方法,暴露在document对象和所有HTML元素上。

getElementsByClassName()方法接收一个参数,即包含一个或多个类名的字符串,返回类名中包含相应类的元素的NodeList。如果提供了多个类名,则顺序是任意的,以空格分隔。

IE9及以上版本,以及所有现代浏览器都支持getElementsByClassName()方法。

classList属性

HTML5通过给所有元素增加classList属性为元素的类名操作提供了更简便的方式。

classList是一个新的集合类型DOMTokenList的实例,与其他DOM集合类型一样,DOMTokenList也有length属性表示自己包含多少项,也可以通过item()或中括号取得个别的元素。

DOMTokenList具有以下方法。

  • add(value):向类名列表中添加指定的字符串值value。如果这个值已经存在,则什么也不做。
  • contains(value):返回布尔值,表示给定的value是否存在。
  • remove(value):从类名列表中删除指定的字符串值value。
  • toggle(value):如果类名列表中已经存在指定的value,则删除,如果不存在,则添加。

有了classList属性之后,除非是完全删除或完全重写元素的class属性,否则className属性就用不到了。

IE10及以上版本部分实现、其他主流浏览器完全实现了classList属性。

焦点管理

activeElement属性

document.activeElement始终为当前页面上拥有焦点的DOM元素的引用。

默认情况下,document.activeElement在页面刚加载完之后会设置为document.body。而在页面完全加载之前,document.activeElement的值为null。

hasFocus方法

document.hasFocus()方法返回布尔值,表示文档是否拥有焦点。通过它可以帮助确定用户是否在操作页面。

无障碍Web应用程序的一个重要方面就是焦点管理。

HTMLDocument扩展

HTML5扩展了HTMLDocument类型,增加了更多功能。

readyState属性

readyState是IE4最早添加到document对象上的属性,最终,HTML5将这个属性写进了标准。

document.readyState属性有两个可能的值:

  • "loading":表示文档正在加载。
  • "complete":表示文档加载完成。

可以将document.readState当成一个指示器,以判断文档是否加载完毕。而在没有该属性之前,只能通过onload事件来进行处理。

compatMode属性

自从IE6提供了以标准或混杂模式渲染页面的能力之后,检测页面渲染模式成为一个必要的需求。

IE最早为document添加了compatMode属性,这个属性唯一的任务是指示浏览器当前处于什么渲染模式。

该属性已经在“BOM”章节介绍过。

HTML5最终也把compatMode属性纳入了标准。

head属性

HTML5增加了document.head属性,指向文档的<head>元素,作为对document.body指向文档的<body>元素的补充。

字符集属性

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

其中主要为characterSet属性。该属性表示文档实际使用的字符集,也可以用来指定新字符集。这个属性的默认值是"UTF-16",但可以通过<meta>元素或响应头,以及characterSet属性来修改。

document.characterSet属性是一个可读可写的属性。

自定义数据属性

HTML5允许给元素指定以前缀data-开始的自定义属性,同时也为它们提供了一个便捷访问的方法。

在DOM对象上使用dataset属性可以访问到元素上的自定义属性。dataset属性是一个DOMStringMap的实例,包含一组键/值对映射。可以通过它来便捷地访问到这些自定义属性。在访问时使用的键是不需要data-前缀的。

自定义数据属性非常适合需要给元素附加某些数据的场景,比如链接追踪和在聚合应用程序中标识页面的不同部分。单页应用程序框架也非常多地使用了自定义数据属性。

插入标签

innerHTML属性

innerHTML属性提供了一次性插入大量元素的能力,而不是通过DOM一个一个添加。

在读取innerHTML属性时,会返回元素所有后代的HTML字符串,包括元素、注释和文本节点。而在写入innerHTML时,则会根据提供的字符串值以新的DOM子树替代元素中原来包含的所有节点。

innerHTML属性实际返回的文本内容会因浏览器而不同。IE和Opera会把所有元素标签转换为大写,而Safari、Chrome和Firefox则会按照文档源代码的格式返回,包含空格和缩进。

同样,在写入innerHTML属性时,转换为DOM的结果也会因浏览器不同而不同。

<script>元素与<style>元素和注释一样,都是“非受控元素”,也就是在页面上看不到它们。而文本节点和其他可见的元素都是“受控元素”。

在所有现代浏览器中,通过innerHTML插入的<script>标签是不会执行的,而在IE8及之前的版本中,只要这样插入的<script>元素指定了defer属性,且<script>前面一个节点是“受控元素”,那这个<script defer>就是可以执行的。

outerHTML属性

读取outerHTML属性时,会返回调用它的元素及所有后代元素的HTML字符串。

outerHTML属性与innerHTML属性的区别就在于它是否包含了调用的DOM节点,其他都是一样的。

两个方法

insertAdjacentHTML()insertAdjacentText()方法,用于插入标签。

这两个方法最早源自IE,在DOM对象上调用它们都需要两个参数:要插入标签的位置和要插入的HTML或文本。

第一个参数必须是下列值中的一个:

  • "beforebegin":插入在当前元素前面,作为前一个兄弟节点。
  • "afterbegin":插入在当前元素内部,作为新的子节点(父节点为空)或放在第一个子节点前面(父节点不为空)。
  • "beforeend":插入在当前元素内部,作为新的子节点(父节点为空)或放在最后一个子节点后面(父节点不为空)。
  • "afterend":插入在当前元素后面,作为下一个兄弟节点。

注意这几个值是不区分大小写的。第二个参数会作为HTML字符串或者作为纯文本解析(与innerText和outerText相同)。如果是HTML,则会在解析出错时抛出错误。

性能与限制

这些插入标签的方法(尤其是innerHTML属性和outerHTML属性)不应当大量使用,因为它们会频繁解析和更替DOM,导致巨大的性能开销。

同时要注意,这样更替了页面DOM之后,被替换掉的DOM包含的事件处理程序、引用关系等仍然存在,可能导致不必要的内存占用,被替换掉的DOM还可能无法被正常垃圾回收。

因此应当手动解除这些关系之后再做替换

此外还需要注意不能把innerHTML属性和outerHTML属性直接暴露给用户来设置,因为它虽然无法执行<script>中的标签,但它却可以创建元素并执行onclick之类的事件来进行攻击,导致潜在的安全问题。

最佳实践是:对于没有绑定额外的事件处理程序和其他引用关系时,对于需要批量替换其内容的DOM对象,在限制使用频率的前提下,可以放心地使用innerHTML属性和outerHTML属性,同时不能直接提供给用户来使用。

滚动到视口

scrollIntoView()方法存在于所有HTML元素上,调用元素的这个方法可以滚动浏览器窗口或容器元素以使该元素进入视口。其参数如下:

  • alignToTop:一个布尔值,可选,默认值为true。
    • true:默认值,窗口滚动后元素的顶部与视口顶部对齐。
    • false:窗口滚动后元素的底部与视口底部对齐。
  • scrollIntoViewOptions:一个选项对象,可选,其有以下选项属性:
    • behavior:定义过渡动画,可取的值为"smooth""auto",默认为"auto"
    • block:定义垂直方向的对齐,可取的值为"start""center""end""nearest",默认为"start"
    • inline:定义水平方向的对齐,可取的值为"start""center""end""nearest",默认为"nearest"

注意滚动的同时并不会设置焦点到目标元素

专有扩展

专有扩展是其他还未被纳入标准的DOM扩展,但这些特性已经被大多浏览器支持。

children属性

children属性是一个HTMLCollection,只包含元素的Element类型的子节点。如果元素的子节点类型全部是元素类型,那children和childNodes中包含的节点应该是一样的。

节点关系

contains方法

DOM编程中经常需要确定一个元素是不是另一个元素的后代。

IE首先引入了contains()方法,在要搜索的祖先元素上调用contains()方法,参数是待确定的目标节点。如果目标节点是被搜索节点的后代,该方法返回true,否则返回false。

compareDocumentPosition方法

compareDocumentPosition()方法是DOM Level 3中规定的,它也可以确定节点间的关系。这个方法会返回表示两个节点关系的位掩码。位掩码的说明如下:

IE9及之后的版本,以及所有现代浏览器都支持contains()compareDocumentPosition()方法。

插入文本

HTML5将IE发明的innerHTML和outerHTML纳入了标准,但还有两个属性没有入选即innerText和outerText。

显然,这两个属性一看就比较鸡肋。

innerText属性

innerText属性对应元素中包含的所有文本内容。

在用于读取值时,innerText会按照深度优先的顺序将子树中所有文本节点的值拼接起来。在用于写入值时,innerText会移除元素的所有后代并插入一个包含该值的文本节点。

innerText目前已经得到所有浏览器支持。

outerText属性

outerText与innerText是类似的,只不过作用范围包含调用它的节点。

要读取文本值时,outerText与innerText会返回同样的内容,在写入文本值时,outerText会把它自己一起替代掉。

outerText是是鸡肋中的鸡肋,没有被标准化的前景,因此不推荐依赖这个属性实现重要的操作。

除Firefox之外所有主流浏览器都支持outerText属性。

滚动到视口

虽然HTML5把scrollIntoView()标准化了,浏览器中仍有其他专有方法。

scrollIntoViewIfNeeded()作为HTMLElement类型的扩展可以在所有元素上调用。

这个方法会在元素不在视口中的情况下,将其滚动到视口中,使其可见;如果已经在视口中可见,则这个方法什么也不做。

它有一个可选的参数alingCenter是个布尔值,设置为true则浏览器会尝试将其放在视口中央,默认是true。

Safari、Chrome和Opera浏览器实现了这个方法。

因为scrollIntoView()方法是唯一一个所有浏览器都支持的方法,所以建议只用scrollIntoView()方法。




* 你好,我是大森。如果文章内容帮到了你,你可通过下方付款二维码支持作者 *