0%

JavaScript学习笔记(11)

《JavaScript高级程序设计》第十一章,DOM扩展。


标准的DOM API有时候满足不了大家的需求,于是便衍生了一些扩展API,最主要的两个扩展:Selectors APIHTML5

选择符API

querySelector()

接收CSS选择符,返回匹配的第一个元素,无则返回null。当使用Document调用此方法时,会在整个文档元素范围内查找;当使用Element调用此方法时,只会在该元素后代元素范围内查找。

querySelectorAll()

接收CSS选择符,返回匹配的所有元素(一个NodeList),无则返回空的NodeList。其它与上一致。访问NodeList里的元素有两种语法:list[i]list.item(i)

matchesSelector()

Element调用此方法,接收CSS选择符,如果调用元素与该选择符匹配则返回true,否则返回false。不过目前这个方法在各个浏览器上暂未统一,比如我用Chrome发现它的该方法是webkitMatchesSelector()

元素遍历

问题源于,对于元素之间的空格,IE9之前的版本不会返回文本节点,而其它所有浏览器都会将将其作为一个文本节点。这样的话,用诸如childNodesfirstChild的属性时候,浏览器行为不一致。因此为了在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()
    之前只有byIdbyTagName。此方法接收包含一个或多个类名的字符串(用空格隔开),返回调用者后代范围内匹配到的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
2
3
4
5
6
7
8
9
10
11
let div = document.getElementById("myDiv");

// get
let appId = div.dataset.appId;
let appName = div.dataset.appName;

// set
div.dataset.appId = 23456;

// check if exists
if(div.dataset.appName) { ... }

插入标记相关

虽然DOM为操作节点提供了很多API,但插入比较麻烦,一是要创建一系列节点,二是要小心地按照顺序插入。相对而言,插入标记的话会比较简单方便。

  • innerHTML

读模式下,返回调用元素的所有后代元素(不包括它本身)。要注意的是不同浏览器返回的文本格式会有所不同,譬如IE和Opera会把标签转为大写,Safari、Chrome和Firefox则返回原本的格式。
写模式下,要注意的是innerHTML的值会被解析为DOM子树,中间会经历字符串的序列化(比如"会变成&quot;),因此设置后获取到的字符串可能和设置的字符串是不一样的。
这个属性也有一些限制:对于IE,如果通过innerHTML插入的字符串开头就是一个“无作用域的元素”(如<script><style>),那这个字符串会在解析之前被删除;对于大多数浏览器,通过innerHTML插入<script>元素不起作用,只有IE在一定条件下(需指定defer属性且<script>元素要位于有作用域元素之后)可以。有几种解决办法:

1
2
3
div.innerHTML = "_<script defer>alert('hi');<\/script>"
div.innerHTML = "<div>&nbsp;<\/div><script defer>alert('hi');<\/script>"
div.innerHTML = "<input type=\"hidden\"><script defer>alert('hi');<\/script>"

除此之外,并不是所有元素都支持innerHTML属性,如<col><colgroup><frameset><head><html><style><table><tbody><thead><tfoot><tr>

通过innerHTML插入的内容需要保证无害,IE8为此提供了一个方法window.toStaticHTML(),用来将传入的HTML字符串转为安全无害版本,即删除所有脚本节点和事件处理程序属性。

1
2
3
let text = "<a href=\"#\" onclick=\"alert('hi')\">Click Me</a>";
let sanitized = window.toStaticHTML(text);
console.log(sanitized); // "<a href=\"#\">Click Me</a>"
  • outerHTML
    读模式下,返回调用它的元素及其所有子节点的HTML标签。
    写模式下,会创建新的DOM子树,并用这个DOM子树完全替换掉调用元素。

  • insertAdjacentHTML()
    接收两个参数:插入位置和需要插入的HTML文本。
    前者有以下选择:

    • "beforebegin":在当前元素之前插入一个紧邻的同辈元素
    • "afterbegin":在当前元素下插入一个新的子元素/在第一个子元素之前插入新的子元素
    • "beforeend":在当前元素下插入一个新的子元素/在最后一个子元素之后插入新的子元素
    • "afterend":在当前元素之后插入一个紧邻的同辈元素

* 内存与性能问题

性能问题一,用以上方法会删除一些带有事件处理程序或引用其他JavaScript对象子树,但删除该元素的同时并不会删除元素与事件处理程序或JavaScript对象之间的绑定关系。因此在使用以上方法时候最好手动鉴别删除一下。
性能问题二,使用innerHTMLouterHTML时,会创建一个HTML解析器,它是浏览器级别的代码(通常用C++写的)基础上运行的,因此比执行JavaScript快。带来的好处是,使用这些方法比通过DOM创建节点再指定它们之间的关系来得快;缺点则是,创建和销毁HTML解析器有性能损失。因此最好能限制一下调用以上方法的次数。

scrollIntoView()

此方法可以在所有HTML元素上调用,通过滚动浏览器窗口或某个容器元素,使得调用元素出现在视口。当传入true或不传参数时,会让调用元素顶部与视口顶部尽量平齐;当传入false时,会让调用元素尽量全部出现在视口(可能的话会让底部平齐)。

专用扩展

也就是不同浏览器自己往DOM中添加的非标准扩展,有些写进标准了,有些没有写进标准成为了自己浏览器专用的内容。这里不太想看,还是略去好了。