DOM 扩展

Selectors API

Selectors API Level 1 的核心是两个方法:

querySelector()和 querySelectorAll()。

在兼容浏览器中,Document 类型和 Element 类型的实例上都会暴露这两个方法。

querySelector()

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

1
2
3
4
5
6
7
8
// 取得<body>元素
let body = document.querySelector("body");
// 取得 ID 为"myDiv"的元素
let myDiv = document.querySelector("#myDiv");
// 取得类名为"selected"的第一个元素
let selected = document.querySelector(".selected");
// 取得类名为"button"的图片
let img = document.body.querySelector("img.button");

在 Document 上使用 querySelector()方法时,会从文档元素开始搜索;

在 Element 上使用 querySelector()方法时,则只会从当前元素的后代中查询

querySelectorAll()

querySelectorAll()方法跟 querySelector()一样,也接收一个用于查询的参数,但它会返回 所有匹配的节点

这个方法返回的是一个 NodeList 的静态实例。

再强调一次,querySelectorAll()返回的NodeList实例一个属性和方法都不缺,但它是一个静态的“快照”,而非“实时”的查询。这样的底层实现避免了使用 NodeList 对象可能造成的性 能问题。

以有效 CSS 选择符调用querySelectorAll()都会返回 NodeList,无论匹配多少个元素都可以。 如果没有匹配项,则返回空的NodeList 实例。

1
2
3
4
5
6
// 取得 ID 为"myDiv"的<div>元素中的所有<em>元素
let ems = document.getElementById("myDiv").querySelectorAll("em");
// 取得所有类名中包含"selected"的元素
let selecteds = document.querySelectorAll(".selected");
// 取得所有是<p>元素子元素的<strong>元素
let strongs = document.querySelectorAll("p strong");

返回的NodeList 对象可以通过 for-of 循环、item()方法或中括号语法取得个别元素

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

matches()

matches()方法(在规范草案中称为 matchesSelector())接收一个CSS选择符参数,如果元素匹配则该选择符返回 true,否则返回false。

1
2
3
if (document.body.matches("body.page1")){ 
// true
}

所有主流浏览器都支持 matches()。

元素遍历

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

为了弥补这个差异:

1.childElementCount,返回子元素数量(不包含文本节点和注释)

2.firstElementChild,指向第一个 Element 类型的子元素(Element 版 firstChild==不包含文本节点和注释)

3.lastElementChild,指向最后一个 Element 类型的子元素(Element 版 lastChild)

4.previousElementSibling ,指向前一个 Element 类型的同胞元素( Element 版 previousSibling)

5.nextElementSibling,指向后一个 Element 类型的同胞元素(Element 版 nextSibling)

在支持的浏览器中,所有 DOM 元素都会有这些属性,为遍历 DOM 元素提供便利。开发者就不用担心空白文本节点的问题

跨浏览器方式遍历特定元素的所有子元素:

1
2
3
4
5
6
7
8
9
10
11
let parentElement = document.getElementById('parent'); 
let currentChildElement = parentElement.firstElementChild;
// 没有子元素,firstElementChild 返回 null,跳过循环
while (currentChildElement) {
// 这就是元素节点,做相应处理
processChild(currentChildElement);
if (currentChildElement === parentElement.lastElementChild) {
break;
}
currentChildElement = currentChildElement.nextElementSibling;
}

HTML5

在所有以前的 HTML 规范中,从未出现过描述 JavaScript 接口的情形,HTML 就是一个纯标记语言。

然而,HTML5 规范却包含了与标记相关的大量 JavaScript API 定义。其中有的 API 与 DOM 重合, 定义了浏览器应该提供的 DOM 扩展。

CSS 类扩展(HTML5 新增的)

  1. getElementsByClassName()

getElementsByClassName()方法接收一个参数,即包含一个或多个类名的字符串,返回类名中包含相应类的元素的 NodeList。如果提供了多个类名,则顺序无关紧要。

1
2
3
4
5
// 取得所有类名中包含"username"和"current"元素
// 这两个类名的顺序无关紧要
let allCurrentUsernames = document.getElementsByClassName("username current");
// 取得 ID 为"myDiv"的元素子树中所有包含"selected"类的元素
let selected = document.getElementById("myDiv").getElementsByClassName("selected");
  1. classList 属性

    通过 className 属性实现添加、删除和替换类名

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

DOMTokenList 还增加了以下方法:

1.add(value),向类名列表中添加指定的字符串值 value。如果这个值已经存在,则什么也不做

2.contains(value),返回布尔值,表示给定的 value 是否存在

3.remove(value),从类名列表中删除指定的字符串值 value

4.toggle(value),如果类名列表中已经存在指定的 value,则删除;如果不存在,则添加。

焦点管理

1.document.activeElement

始终包含当前拥有焦点的 DOM 元素

焦点元素:页面加载时,可以通过用户输入(按 Tab 键或代码中使用 focus()方法)让某个元素自动获得焦点。

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

2.document.hasFocus()

该方法返回布尔值,表示文档是否拥有焦点:

确定文档是否获得了焦点,就可以帮助确定用户是否在操作页面

HTMLDocument 扩展

1.readyState 属性

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

 loading,表示文档正在加载;

 complete,表示文档加载完成

实际开发中,最好是把 document.readState 当成一个指示器,以判断文档是否加载完毕。

这个属性的基本用法如下:

1
2
3
if (document.readyState == "complete"){ 
// 执行操作
}

2.compatMode 属性

这个属性唯一的任务是指示浏览器当前处于什么渲染模式

标准模式下 document.compatMode 的值是”CSS1Compat”,而在混杂模式下, document.compatMode 的值是”BackCompat”

3.head 属性

作为对 document.body(指向文档的元素)的补充,HTML5 增加了 document.head 属 性,指向文档的元素。可以像下面这样直接取得元素:

1
let head = document.head; 

4.characterSet

characterSet 属性表示文档实际使用的 字符集,也可以用来指定新字符集。这个属性的默认值是”UTF-16”,但可以通过元素或响应头, 以及新增的 characterSeet 属性来修改。

5.自定义数据属性

HTML5 允许给元素指定非标准的属性,但要使用前缀 data-以便告诉浏览器

除了前缀,自定义属性对命名是没有限制的,data-后面跟什么都可以。

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

定义了自定义数据属性后,可以通过元素的 dataset 属性来访问。dataset 属性是一个 DOMStringMap 的实例,包含一组键/值对映射。

下面是一个使用自定义数据属性的例子:

1
2
3
4
5
6
7
8
9
10
11
let div = document.getElementById("myDiv"); 
// 取得自定义数据属性的值
let appId = div.dataset.appId;
let myName = div.dataset.myname;
// 设置自定义数据属性的值
div.dataset.appId = 23456;
div.dataset.myname = "Michael";
// 有"myname"吗?
if (div.dataset.myname){
console.log(`Hello, ${div.dataset.myname}`);
}

自定义数据属性非常适合需要给元素附加某些数据的场景,比如

1.链接追踪和在聚合应用程序中标识页面的不同部分

2.单页应用程序框架

6.插入标记

向文档中一次性插入大量 HTML 时还是比较麻烦,相比先创建一堆节点,再把它们以正确的顺序连接起来,直接插入一个 HTML 字符串要简单(快速) 得多.

1.innerHTML 属性

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

1
2
3
4
5
6
7
8
<div id="content"> 
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>

对于这里的元素而言,其 innerHTML 属性会返回以下字符串:

1
2
3
4
5
6
p>This is a <strong>paragraph</strong> with a list following it.</p> 
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>

实际返回的文本内容会因浏览器而不同

IE 和 Opera 会把所有元素标签转换为大写,而 Safari、 Chrome 和 Firefox 则会按照文档源代码的格式返回,包含空格和缩进.

在写入模式下,赋给 innerHTML 属性的值会被解析为 DOM 子树,并替代元素之前的所有节点。 因为所赋的值默认为 HTML

如果赋值中不包含任何 HTML 标签,则直接生成一个文本节点, 如下所示:

1
div.innerHTML = "Hello world!";

2.outerHTML 属性

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

在写入 outerHTML 属性时,调用它的元素会被传入的 HTML 字符串经解释之后生成的 DOM 子树取代。

1
2
3
4
5
6
7
8
<div id="content"> 
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>

在这个<div>元素上调用 outerHTML 会返回相同的字符串,包括<div>本身。

3.insertAdjacentHTML()与 insertAdjacentText()

插入标签的最后两个新增方法是 insertAdjacentHTML()和 insertAdjacentText(),

它们都接收两个参数:要插入标记的位置和要插入的 HTML 或文本。

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

1.”beforebegin”,插入当前元素前面,作为前一个同胞节点;

2.”afterbegin”,插入当前元素内部,作为新的子节点或放在第一个子节点前面

3.”beforeend”,插入当前元素内部,作为新的子节点或放在最后一个子节点后面

4.”afterend”,插入当前元素后面,作为下一个同胞节点

4.内存与性能

HTML 解析器的构建与解构也不是没有代价,因此最好限制使用 innerHTML 和 outerHTML 的次数。

1
2
3
for (let value of values){ 
ul.innerHTML += '<li>${value}</li>'; // 别这样做!
}

这段代码效率低,因为每次迭代都要设置一次 innerHTML。不仅如此,每次循环还要先读取 innerHTML,也就是说循环一次要访问两次 innerHTML。为此,最好通过循环先构建一个独立的字符 串,最后再一次性把生成的字符串赋值给 innerHTML

1
2
3
4
5
let itemsHtml = "";
for (let value of values){
itemsHtml += '<li>${value}</li>';
}
ul.innerHTML = itemsHtml;

这样修改之后效率就高多了,因为只有对 innerHTML 的一次赋值。

5.跨站点脚本

如果页面中要使用用户提供的信息,则不建议使用 innerHTML。

目的:防止 XSS 攻击

6.scrollIntoView()

scrollIntoView()方法存在于所有 HTML 元素上,可以滚动浏览器窗口或容器元素以便包含元 素进入视口。

两个参数:

1.alignToTop

true:窗口滚动后元素的顶部与视口顶部对齐。

false:窗口滚动后元素的底部与视口底部对齐。

2.scrollIntoViewOptions

behavior:定义过渡动画,可取的值为”smooth”和”auto”,默认为”auto”。

 block:定义垂直方向的对齐,可取的值为”start”、”center”、”end”和”nearest”,默认为 “start”。

 inline:定义水平方向的对齐,可取的值为”start”、”center”、”end”和”nearest”,默认为 “nearest”。

不传参数等同于 alignToTop 为 true

专有扩展

1**.children** 属性是一个 HTMLCollection,只包含元素的 Element 类型的子节点。

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

3.插入标记,innerText 属性和outerText 属性

1
2
3
4
5
6
7
8
<div id="content"> 
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>

对这个例子中的<div>而言,innerText 属性会返回以下字符串:

This is a paragraph with a list following it.

Item 1

Item 2

Item 3

下面再看一个使用 innerText 设置<div>元素内容的例子

1
div.innerText = "Hello world!"; 

执行这行代码后,HTML 页面中的这个<div>元素实际上会变成这个样子:

1
<div id="content">Hello world!</div>