任何 HTML 或 XML 文档都可以用 DOM 表示为一个由节点构成的层级结构。节点分很多类型,每种类型对应着文档中不同的信息和(或)标记,也都有自己不同的特性、数据和方法,而且与其他类型有某种关系。这些关系构成了层级,让标记可以表示为一个以特定节点为根的树形结构。
document 节点是每个文档的根节点。根节点的唯一子节点是< html>元素,称文档元素(documentElement),是文档最外层的元素,所有其他元素都存在于这个元素之内,每个文档只能有一个文档元素。在 HTML 页面中,文档元素始终是< html>元素。在 XML 文档中,则没有这样预定义的元素,任何元素都可能成为文档元素。
DOM Level 1 描述了名为Node的接口,该接口是所有DOM节点类型都必须实现的。Node接口在JavaScript中被实现为Node类型,在除IE之外的所有浏览器中都可以直接访问这个类型。在JavaScript中,所有节点类型都继承Node类型,因此所有类型都共享相同的基本属性和方法。
if (someNode.nodeType == 1) {
value = someNode.nodeName; // 会显示元素的标签名
}
Document类型是JS中表示文档节点的类型。在浏览器中,文档对象document是HTMLDocument的实例(HTMLDocument继承Document),表示整个HTML页面。document是window对象的属性,因此是一个全局对象。document 对象可用于获取关于页面的信息以及操纵其外观和底层结构。
document.implementation 属性是一个对象,其中提供了与浏览器 DOM 实现相关的信息和能力。DOM Level 1 在 document.implementation 上只定义了一个方法 hasFeature(),接收两个参数:特性名称和 DOM 版本。由于实现不一致,因此 hasFeature()的返回值并不可靠。
Element 类型就是 Web 开发中最常用的类型了。Element 表示 XML 或 HTML 元素,对外暴露出访问元素标签名、子节点和属性的能力。可以通过 nodeName 或 tagName 属性来获取元素的标签名。在 HTML 中,元素标签名始终以全大写表示;在 XML(包括 XHTML)中,标签名始终与源代码中的大小写一致。
所有 HTML 元素都通过 HTMLElement 类型表示,包括其直接实例和间接实例(即HTMLElement 或其子类型的实例)。
每个元素都有零个或多个属性,通常用于为元素或其内容附加更多信息
Element 类型是唯一使用attributes属性的DOM节点类型。attributes属性包含一个NamedNodeMap 实例,是一个类似NodeList的“实时”集合。元素的每个属性都表示为一个Attr节点,并保存在这个NamedNodeMap对象中。
可以使用document.createElement()方法创建新元素。接收一个参数,即要创建元素的标签名。在HTML文档中,标签名是不区分大小写的,而XML文档(包括XHTML)是区分大小写的。
元素可以拥有任意多个子元素和后代元素,因为元素本身也可以是其他元素的子元素。 childNodes属性包含元素所有的子节点,这些子节点可能是其他元素、文本节点、注释或处理指令。
Text节点由Text类型表示,包含按字面解释的纯文本,也可能包含转义后的HTML字符,但不含HTML代码。Text节点中包含的文本可以通过nodeValue属性访问,也可以通过data属性访问。
document.createTextNode()可以用来创建新文本节点,它接收一个参数,即要插入节点的文本。一般来说一个元素只包含一个文本子节点。不过,也可以让元素包含多个文本子节点。
DOM 中的注释通过 Comment 类型表示。与 Text 类型继承同一个基类( CharacterData),因此拥有除 splitText()之外Text 节点所有的字符串操作方法。与 Text 类型相似,注释的实际内容可以通过 nodeValue 或 data 属性获得。
CDATASection 类型表示 XML 中特有的 CDATA 区块。 CDATASection 类型继承 Text 类型,拥有包括 splitText()在内的所有字符串操作方法。
DocumentType 类型的节点包含文档的文档类型( doctype)信息
<!DOCTYPE HTML PUBLIC "-// W3C// DTD HTML 4.01// EN"
"http:// www.w3.org/TR/html4/strict.dtd">
<!-- 对于这个文档类型, name 属性的值是"html" -->
# DocumentFragment类型
DocumentFragment 类型是唯一一个在标记中没有对应表示的类型。DOM将文档片段定义为“轻量级”文档,能够包含和操作节点,却没有完整文档那样额外的消耗。不能直接把文档片段添加到文档。相反,文档片段的作用是充当其他要被添加到文档的节点的仓库。
// 如果分 3 次给这个元素添加列表项,浏览器就要重新渲染3 次页面,以反映新添加的内容
// 利用文档片段可避免多次渲染
let fragment = document.createDocumentFragment();
let ul = document.getElementById("myList");
for (let i = 0; i < 3; ++i) {
let li = document.createElement("li");
li.appendChild(document.createTextNode(`Item ${i + 1}`));
fragment.appendChild(li);
}
ul.appendChild(fragment);
元素数据在 DOM 中通过 Attr 类型表示。 Attr 类型构造函数和原型在所有浏览器中都可以直接访问。技术上讲,属性是存在于元素 attributes 属性中的节点。
动态脚本就是在页面初始加载时不存在,之后又通过 DOM 包含的脚本。有两种方式通过< script>动态为网页添加脚本:引入外部文件和直接插入源代码。
通过 innerHTML 属性创建的< script>元素永远不会执行。浏览器会尽责地创建< script>元素,以及其中的脚本文本,但解析器会给这个< script>元素打上永不执行的标签。
< link>元素用于包含 CSS 外部文件, 而< style>元素用于添加嵌入样式。动态样式也是页面初始加载时并不存在,而是在之后才添加到页面中的。
// 注意应该把<link>元素添加到<head>元素而不是<body>元素,这样才能保证所有浏览器都能正常运行
function loadStyles(url){
let link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = url;
let head = document.getElementsByTagName("head")[0];
head.appendChild(link);
}
// style
let style = document.createElement("style");
style.type = "text/css";
style.appendChild(document.createTextNode("body{background-color:red}"));
let head = document.getElementsByTagName("head")[0];
head.appendChild(style);
为了方便创建表格, HTML DOM 给< table>、 < tbody>和< tr>元素添加了一些属性和方法
NodeList 是基于 DOM 文档的实时查询。
MutationObserver 接口,可以在 DOM 被修改时异步执行回调。使用 MutationObserver 可以观察整个文档、 DOM 树的一部分,或某个元素。还可以观察元素属性、子节点、文本,或者前三者任意组合的变化。
新创建的 MutationObserver 实例不会关联 DOM 的任何部分。要把这个 observer 与 DOM 关联起来,需要使用 observe()方法。这个方法接收两个必需的参数:要观察其变化的 DOM 节点,以及一个 MutationObserverInit 对象。MutationObserverInit 对象用于控制观察哪些方面的变化, 是一个键/值对形式配置选项的字典。
let observer = new MutationObserver(() => console.log('<body> attributes changed'));
observer.observe(document.body, { attributes: true });
document.body.className = 'foo';
console.log('Changed body class');
// Changed body class
// <body> attributes changed
每个回调都会收到一个 MutationRecord 实例的数组。 MutationRecord 实例包含的信息包括发生了什么变化,以及 DOM 的哪一部分受到了影响。因为回调执行之前可能同时发生多个满足观察条件的事件,所以每次执行回调都会传入一个包含按顺序入队的 MutationRecord 实例的数组。连续修改会生成多个 MutationRecord 实例,下次回调执行时就会收到包含所有这些实例的数组,顺序为变化事件发生的顺序。传给回调函数的第二个参数是观察变化的 MutationObserver 的实例。
let observer = new MutationObserver((mutationRecords) => console.log(mutationRecords));
observer.observe(document.body, { attributes: true });
document.body.setAttribute('foo', 'bar');
// [
// {
// addedNodes: NodeList [],
// attributeName: 'foo',
// attributeNamespace: null,
// nextSibling: null,
// oldValue: null,
// previousSibling: null,
// removedNodes: NodeList [],
// target: body,
// type: 'attributes'
// }
// ]
document.body.setAttributeNS('baz', 'foo', 'bar'); // 涉及命名空间的变化
// [
// {
// addedNodes: NodeList [],
// attributeName: "foo",
// attributeNamespace: "baz",
// nextSibling: null,
// oldValue: null,
// previousSibling: null
// removedNodes: NodeList [],
// target: body
// type: "attributes"
// }
// ]
默认情况下,只要被观察的元素不被垃圾回收, MutationObserver 的回调就会响应 DOM 变化事件,从而被执行。要提前终止执行回调,可以调用 disconnect()方法。同步调用disconnect()之后,不仅会停止此后变化事件的回调,也会抛弃已经加入任务队列要异步执行的回调。
let observer = new MutationObserver(() => console.log('<body> attributes changed'));
observer.observe(document.body, { attributes: true });
document.body.className = 'foo';
observer.disconnect();
document.body.className = 'bar';
//(没有日志输出)
要想让已经加入任务队列的回调执行,可以使用setTimeout()让已经入列的回调执行完毕再调用disconnect()
let observer = new MutationObserver(() => console.log('<body> attributes changed'));
observer.observe(document.body, { attributes: true });
document.body.className = 'foo';
setTimeout(() => {
observer.disconnect();
document.body.className = 'bar';
}, 0);
// <body> attributes changed
多次调用observe()方法,可以复用一个MutationObserver对象观察多个不同的目标节点。此时, MutationRecord的target属性可以标识发生变化事件的目标节点。disconnect()方法是一个“一刀切”的方案,调用它会停止观察所有目标。
let observer = new MutationObserver((mutationRecords) =>
console.log(mutationRecords.map((x) => x.target)));
// 向页面主体添加两个子节点
let childA = document.createElement('div'),
childB = document.createElement('span');
document.body.appendChild(childA);
document.body.appendChild(childB);
// 观察两个子节点
observer.observe(childA, { attributes: true });
observer.observe(childB, { attributes: true });
// 修改两个子节点的属性
childA.setAttribute('foo', 'bar');
childB.setAttribute('foo', 'bar');
// [<div>, <span>]
调用disconnect()并不会结束MutationObserver的生命。还可以重新使用这个观察者,再将它关联到新的目标节点。
MutationObserverInit对象用于控制对目标节点的观察范围。粗略地讲,观察者可以观察的事件包括属性变化、文本变化和子节点变化。
MutationObserver可以观察节点属性的添加、移除和修改。要为属性变化注册回调,需要在MutationObserverInit对象中将attributes属性设置为true
MutationObserver可以观察文本节点(如Text、Comment或ProcessingInstruction节点)中字符的添加、删除和修改。要为字符数据注册回调,需要在MutationObserverInit对象中将characterData属性设置为true
MutationObserver可以观察目标节点子节点的添加和移除。要观察子节点,需要在MutationObserverInit对象中将childList属性设置为true。对子节点重新排序(尽管调用一个方法即可实现)会报告两次变化事件,因为从技术上会涉及先移除和再添加
默认情况下, MutationObserver将观察的范围限定为一个元素及其子节点的变化。可以把观察的范围扩展到这个元素的子树(所有后代节点),这需要在 MutationObserverInit对象中将subtree属性设置为true。被观察子树中的节点被移出子树之后仍然能够触发变化事件。
MutationObserver接口是出于性能考虑而设计的,其核心是异步回调与记录队列模型。为了在大量变化事件发生时不影响性能,每次变化的信息(由观察者实例决定)会保存在MutationRecord实例中,然后添加到记录队列。这个队列对每个MutationObserver实例都是唯一的,是所有DOM变化事件的有序列表。
每次 MutationRecord 被添加到 MutationObserver 的记录队列时,仅当之前没有已排期的微任务回调时(队列中微任务长度为 0),才会将观察者注册的回调(在初始化 MutationObserver 时传入)作为微任务调度到任务队列上。这样可以保证记录队列的内容不会被回调处理两次。
调用 MutationObserver 实例的 takeRecords()方法可以清空记录队列,取出并返回其中的所有 MutationRecord 实例。
接收CSS选择符参数,返回匹配该模式的第一个后代元素,如果没有匹配项则返回null。在 Document上使用 querySelector()方法时,会从文档元素开始搜索;在Element上使用querySelector()方法时,则只会从当前元素的后代中查询。
接收一个用于查询的参数,返回所有匹配的节点(一个 NodeList 的静态实例,但是是静态的“快照”,而非“实时”的查询)。可以在Document、DocumentFragment和Element类型上使用。
接收一个CSS选择符参数,如果元素匹配则该选择符返回true,否则返回false。使用这个方法可以方便地检测某个元素会不会被querySelector()或querySelectorAll()方法返回。
IE9之前的版本不会把元素间的空格当成空白节点,而其他浏览器则会。这样就导致了childNodes和firstChild等属性上的差异。为了弥补这个差异,同时不影响DOM规范,W3C通过新的Element Traversal规范定义了一组新属性,为遍历 DOM 元素提供便利。
Element Traversal API 为 DOM 元素添加了 5 个属性:
// 取得所有类名中包含"username"和"current"元素
// 这两个类名的顺序无关紧要
let allCurrentUsernames = document.getElementsByClassName("username current");
// 取得 ID 为"myDiv"的元素子树中所有包含"selected"类的元素
let selected = document.getElementById("myDiv").getElementsByClassName("selected");
要操作类名,可以通过 className 属性实现添加、删除和替换。className 是一个字符串,所以每次操作之后都需要重新设置这个值才能生效,即使只改动了部分字符串也一样。HTML5 通过给所有元素增加 classList 属性为这些操作提供了更简单也更安全的实现方式。
classList 是一个新的集合类型 DOMTokenList 的实例,DOMTokenList有length属性表示自己包含多少项,可以通过 item()或中括号取得个别的元素。
HTML5 增加了辅助 DOM 焦点管理的功能。首先是 document.activeElement,始终包含当前拥有焦点的 DOM 元素。
作为对 document.body(指向文档的< body>元素)的补充, HTML5 增加了 document.head 属性,指向文档的< head>元素。
characterSet 属性表示文档实际使用的字符集,也可以用来指定新字符集。这个属性的默认值是"UTF-16",但可以通过< meta>元素或响应头,以及新增的 characterSeet 属性来修改。
console.log(document.characterSet); // "UTF-16"
document.characterSet = "UTF-8";
HTML5 允许给元素指定非标准的属性,但要使用前缀 data-以便告诉浏览器,这些属性既不包含与渲染有关的信息,也不包含元素的语义信息。
在读取 innerHTML 属性时,会返回元素所有后代的 HTML 字符串,包括元素、注释和文本节点。而在写入 innerHTML 时,则会根据提供的字符串值以新的 DOM 子树替代元素中原来包含的所有节点。如果赋值中不包含任何 HTML 标签,则直接生成一个文本节点。
在所有现代浏览器中,通过innerHTML插入的< script>标签是不会执行的。而在IE8及之前的版本中,只要这样插入的< script>元素指定了defer属性,且< script>之前是“受控元素”(scoped element),那就是可以执行的。< script>元素与< style>或注释一样,都是“非受控元素”(NoScope element),也就是在页面上看不到它们。
读取 outerHTML 属性时,会返回调用它的元素(及所有后代元素)的 HTML 字符串。在写入outerHTML 属性时,调用它的元素会被传入的 HTML 字符串经解释之后生成的 DOM 子树取代。
替换子节点可能在浏览器(特别是 IE)中导致内存问题。如果这种替换操作频繁发生,页面的内存占用就会持续攀升。在使用 innerHTML、outerHTML 和 insertAdjacentHTML()之前,最好手动删除要被替换的元素上关联的事件处理程序和 JavaScript 对象。
一般来讲,插入大量的新HTML使用innerHTML比使用多次DOM操作创建节点再插入来得更便捷。因为HTML解析器会解析设置给 innerHTML(或 outerHTML)的值。解析器在浏览器中是底层代码(通常是 C++代码),比JavaScript快得多。
如果页面中要使用用户提供的信息,则不建议使用innerHTML。与使用innerHTML获得的方便相比,防止XSS攻击更让人头疼。此时一定要隔离要插入的数据,在插入页面前必须毫不犹豫地使用相关的库对它们进行转义。
scrollIntoView()方法存在于所有HTML元素上,可以滚动浏览器窗口或容器元素以便包含元素进入视口。参数如下:
children 属性是一个 HTMLCollection,只包含元素的 Element 类型的子节点。如果元素的子节点类型全部是元素类型,那 children 和 childNodes 中包含的节点应该是一样的。
contains()方法应该在要搜索的祖先元素上调用,参数是待确定的目标节点。如果目标节点是被搜索节点的后代, contains()返回 true,否则返回 false。
使用 DOM Level 3 的 compareDocumentPosition()方法也可以确定节点间的关系。该方法会返回表示两个节点关系的位掩码。
innerText 属性对应元素中包含的所有文本内容,无论文本在子树中哪个层级。在用于读取值时,innerText 会按照深度优先的顺序将子树中所有文本节点的值拼接起来。在用于写入值时,innerText会移除元素的所有后代并插入一个包含该值的文本节点。
outerText 与 innerText 是类似的,只不过作用范围包含调用它的节点。要读取文本值时,outerText 与 innerText 实际上会返回同样的内容。但在写入文本值时, outerText 就大不相同了。写入文本值时, outerText 不止会移除所有后代节点,而是会替换整个元素。
虽然 HTML5 把scrollIntoView()标准化了,但不同浏览器中仍然有其他专有方法。比如, scrollIntoViewIfNeeded()作为HTMLElement 类型的扩展可以在所有元素上调用。 scrollIntoViewIfNeeded(alingCenter)会在元素不可见的情况下,将其滚动到窗口或包含窗口中,使其可见;如果已经在视口中可见,则这个方法什么也不做。如果将可选的参数 alingCenter 设置为 true,则浏览器会尝试将其放在视口中央。
XML命名空间可以实现在一个格式规范的文档中混用不同的XML语言,而不必担心元素命名冲突。严格来讲,XML命名空间在XHTML中才支持,HTML并不支持。
命名空间是使用 xmlns 指定的。 XHTML 的命名空间是"http://www.w3.org/1999/xhtml",应该包含在任何格式规范的 XHTML 页面的< html>元素中,可以使用 xmlns 给命名空间创建一个前缀,格式为“xmlns: 前缀”,为避免混淆,属性也可以加上命名空间前缀。
如果文档中只使用一种 XML 语言,那么命名空间前缀其实是多余的,只有一个文档混合使用多种 XML 语言时才有必要。
在节点使用命名空间前缀的情况下, nodeName 等于 prefix + ":" + localName。 DOM3 进一步增加了如下与命名空间相关的方法:
Document 类型的更新中唯一跟命名空间无关的方法是 importNode()。这个方法的目的是从其他文档获取一个节点并导入到新文档,以便将其插入新文档。每个节点都有一个 ownerDocument 属性,表示所属文档。如果调用 appendChild()方法时传入节点的 ownerDocument 不是指向当前文档,则会发生错误。而调用importNode()导入其他文档的节点会返回一个新节点,这个新节点的 ownerDocument 属性是正确的。
importNode()方法跟 cloneNode()方法类似,同样接收两个参数:要复制的节点和表示是否同时复制子树的布尔值,返回结果是适合在当前文档中使用的新节点。
DOM3 新增了两个用于比较节点的方法: isSameNode()和 isEqualNode()。两个方法都接收一个节点参数,如果这个节点与参考节点相同或相等,则返回 true。节点相同,意味着引用同一个对象;节点相等,意味着节点类型相同,拥有相等的属性( nodeName、 nodeValue 等),而且 attributes 和 childNodes 也相等(即同样的位置包含相等的值)。
DOM3 也增加了给 DOM 节点附加额外数据的方法。 setUserData()方法接收 3 个参数:键、值、处理函数,用于给节点追加数据。处理函数会在包含数据的节点被复制、删除、重命名或导入其他文档的时候执行,可以在这时候决定如何处理用户数据。
DOM2 HTML 给 HTMLIFrameElement(即< iframe>,内嵌窗格)类型新增了一个属性,叫 contentDocument。这个属性包含代表子内嵌窗格中内容的 document 对象的指针。contentDocument 属性是 Document 的实例,拥有所有文档属性和方法,因此可以像使用其他 HTML 文档一样使用它。还有一个属性 contentWindow,返回相应窗格的 window 对象,这个对象上有一个 document 属性。
HTML 中的样式有 3 种定义方式:外部样式表(通过< link>元素)、文档样式表(使用< style>元素)和元素特定样式(使用 style 属性)。
任何支持 style 属性的 HTML 元素在 JavaScript 中都会有一个对应的 style 属性。这个 style 属性是CSSStyleDeclaration 类型的实例,其中包含通过 HTML style 属性为元素设置的所有样式信息,但不包含通过层叠机制从文档样式和外部样式中继承来的样式。HTML style 属性中的 CSS 属性在 JavaScript style 对象中都有对应的属性。因为 CSS 属性名使用连字符表示法(用连字符分隔两个单词 , 如 background-image),所以在JavaScript 中这些属性必须转换为驼峰大小写形式(如backgroundImage)。
大多数属性名会这样直接转换过来。但有一个 CSS 属性名不能直接转换, 因为float 是 JavaScript 的保留字,所以不能用作属性名。DOM2 Style 规定它在 style 对象中对应的属性应该是 cssFloat。
style 对象中包含支持 style 属性的元素为这个属性设置的样式信息,但不包含从其他样式表层叠继承的同样影响该元素的样式信息。DOM2 Style在 document.defaultView 上增加了 getComputedStyle()方法。这个方法接收两个参数:要取得计算样式的元素和伪元素字符串(如":after")。如果不需要查询伪元素,则第二个参数可以传 null。 getComputedStyle()方法返回一个 CSSStyleDeclaration 对象(与 style 属性的类型一样),包含元素的计算样式。
CSSStyleSheet 类型表示 CSS 样式表,包括使用< link>(HTMLLinkElement)元素和通过< style>(HTMLStyleElement)元素定义的样式表。CSSStyleSheet 类型是一个通用样式表类型,可以表示以任何方式在 HTML 中定义的样式表。另外,元素特定的类型允许修改 HTML 属性,而 CSSStyleSheet 类型的实例则是一个只读对象(只有一个属性例外,disabled)。通过< link>或< style>元素也可以直接获取 CSSStyleSheet 对象。 DOM 在这两个元素上暴露了 sheet 属性,其中包含对应的 CSSStyleSheet 对象。 CSSStyleSheet类型继承StyleSheet,后者可用作非 CSS样式表的基类。以下是CSSStyleSheet从 StyleSheet 继承的属性:
sheet.insertRule("body { background-color: silver }", 0); // 使用 DOM 方法
sheet.deleteRule(0); // 使用 DOM 方法
要确定一个元素在页面中的偏移量,可以把它的 offsetLeft 和 offsetTop 属性分别与 offsetParent 的相同属性相加,一直加到根元素。
客户端尺寸实际上就是元素内部的空间,因此不包含滚动条占用的空间。这两个属性最常用于确定浏览器视口尺寸,即检测 document.documentElement 的 clientWidth 和 clientHeight。这两个属性表示视口(< html>或< body>元素)的尺寸。
DOM2 Traversal and Range 模块定义了两个类型用于辅助顺序遍历 DOM 结构。这两个类型—— NodeIterator 和 TreeWalker——从某个起点开始执行对 DOM 结构的深度优先遍历。
通过 document.createNodeIterator()方法创建其实例,收4个参数
TreeWalker 是 NodeIterator 的高级版,添加了如下在 DOM 结构中向不同方向遍历的方法
TreeWalker 真正的威力是可以在 DOM 结构中四处游走。TreeWalker 类型也有一个名为 currentNode 的属性,表示遍历过程中上一次返回的节点(无论使用的是哪个遍历方法)。可以通过修改这个属性来影响接下来遍历的起点。
DOM2 在 Document 类型上定义了一个 createRange()方法,暴露在 document 对象上。使用这个方法可以创建一个 DOM 范围对象。与节点类似,这个新创建的范围对象是与创建它的文档关联的,不能在其他文档中使用。然后可以使用这个范围在后台选择文档特定的部分。创建范围并指定它的位置之后,可以对范围的内容执行一些操作,从而实现对底层 DOM 树更精细的控制。 每个范围都是 Range 类型的实例,拥有相应的属性和方法。
通过范围选择文档中某个部分最简单的方式,就是使用 selectNode()或 selectNodeContents()方法。这两个方法都接收一个节点作为参数,并将该节点的信息添加到调用它的范围。
let range1 = document.createRange(),
range2 = document.createRange(),
p1 = document.getElementById("p1");
range1.selectNode(p1);
range2.selectNodeContents(p1);
选定节点或节点后代之后,还可以在范围上调用相应的方法,实现对范围中选区的更精细控制
要创建复杂的范围,需要使用 setStart()和 setEnd()方法。两个方法都接收两个参数:参照节点和偏移量。对 setStart()来说,参照节点会成为 startContainer,而偏移量会赋值给 startOffset。对 setEnd()而言,参照节点会成为 endContainer,而偏移量会赋值给 endOffset。
创建范围之后,浏览器会在内部创建一个文档片段节点,用于包含范围选区中的节点。为操作范围的内容,选区中的内容必须格式完好。不过,范围能够确定缺失的开始和结束标签,从而可以重构出有效的 DOM 结构,以便后续操作。
如果范围并没有选择文档的任何部分,则称为折叠(collapsed)。
如果有多个范围,则可以使用 compareBoundaryPoints()方法确定范围之间是否存在公共的边界(起点或终点)。这个方法接收两个参数:要比较的范围和一个常量值,表示比较的方式。
调用范围的cloneRange()方法可以复制范围。这个方法会创建调用它的范围的副本
在使用完范围之后,最好调用detach()方法把范围从创建它的文档中剥离。调用detach()之后,就可以放心解除对范围的引用,以便垃圾回收程序释放它所占用的内存。