DOM 上
约 13098 字大约 44 分钟
JavaScript
2025-05-15
概述
DOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如增删内容)。
节点
DOM 的最小组成单位叫做节点(node)。文档的树形结构(DOM 树),就是由各种不同类型的节点组成。每个节点可以看作是文档树的一片叶子。
节点的类型有七种。
Document:整个文档树的顶层节点DocumentType:doctype标签(比如<!DOCTYPE html>)Element:网页的各种HTML标签(比如<body>、<a>等)Attr:网页元素的属性(比如class="right")Text:标签之间或标签包含的文本Comment:注释DocumentFragment:文档的片段
浏览器提供一个原生的节点对象Node,上面这七种节点都继承了Node,因此具有一些共同的属性和方法。
提示
DocumentFragment 是 DOM(文档对象模型)中的一个特殊节点类型,它代表一个轻量级的文档片段。
DocumentFragment 节点本身不是一个真实的 DOM 节点,因此它不会直接出现在浏览器的渲染树中。它主要用于临时存储和管理一组节点,以便在需要时一次性将它们添加到实际的 DOM 树中
节点树
一个文档的所有节点,按照所在的层级,可以抽象成一种树状结构。
这种树状结构就是 DOM 树。它有一个顶层节点,下一层都是顶层节点的子节点,然后子节点又有自己的子节点,就这样层层衍生出一个金字塔结构,又像一棵树。
浏览器原生提供document节点,代表整个文档。
document
// 整个文档树
#document文档的第一层有两个节点,第一个是文档类型节点(<!doctype html>),第二个是 HTML 网页的顶层容器标签<html>。后者构成了树结构的根节点(root node),其他 HTML 标签节点都是它的下级节点。
除了根节点,其他节点都有三种层级关系。
- 父节点关系(parentNode):直接的那个上级节点
- 子节点关系(childNodes):直接的下级节点
- 同级节点关系(sibling):拥有同一个父节点的节点
DOM 提供操作接口,用来获取这三种关系的节点。
比如,子节点接口包括firstChild(第一个子节点)和lastChild(最后一个子节点)等属性,同级节点接口包括nextSibling(紧邻在后的那个同级节点)和previousSibling(紧邻在前的那个同级节点)属性。
Node 属性
nodeType
Node.prototype.nodeType属性返回一个整数值,表示节点的类型。
document.nodeType // 9上面代码中,文档节点的类型值为9。
Node 对象定义了几个常量,对应这些类型值。
document.nodeType === Node.DOCUMENT_NODE // true上面代码中,文档节点的nodeType属性等于常量Node.DOCUMENT_NODE。
不同节点的nodeType属性值和对应的常量如下。
- 文档节点(document):9,对应常量
Node.DOCUMENT_NODE - 元素节点(element):1,对应常量
Node.ELEMENT_NODE - 属性节点(attr):2,对应常量
Node.ATTRIBUTE_NODE - 文本节点(text):3,对应常量
Node.TEXT_NODE - 文档片断节点(DocumentFragment):11,对应常量
Node.DOCUMENT_FRAGMENT_NODE - 文档类型节点(DocumentType):10,对应常量
Node.DOCUMENT_TYPE_NODE - 注释节点(Comment):8,对应常量
Node.COMMENT_NODE
nodeName
Node.prototype.nodeName属性返回节点的名称。
// HTML 代码如下
// <div id="d1">hello world</div>
var div = document.getElementById('d1');
div.nodeName // "DIV"上面代码中,元素节点<div>的nodeName属性就是大写的标签名DIV。
不同节点的nodeName属性值如下。
- 文档节点(document):
#document - 元素节点(element):大写的标签名
- 属性节点(attr):属性的名称
- 文本节点(text):
#text - 文档片断节点(DocumentFragment):
#document-fragment - 文档类型节点(DocumentType):文档的类型
- 注释节点(Comment):
#comment
nodeValue
Node.prototype.nodeValue属性返回一个字符串,表示当前节点本身的文本值,该属性可读写。
只有文本节点(text)、注释节点(comment)和属性节点(attr)有文本值,因此这三类节点的nodeValue可以返回结果,其他类型的节点一律返回null。
同样的,也只有这三类节点可以设置nodeValue属性的值,其他类型的节点设置无效。
// HTML 代码如下
// <div id="d1">hello world</div>
var div = document.getElementById('d1');
div.nodeValue // null
div.firstChild.nodeValue // "hello world"上面代码中,div是元素节点,nodeValue属性返回null。div.firstChild是文本节点,所以可以返回文本值。
textContent
Node.prototype.textContent属性返回当前节点和它的所有后代节点的文本内容。
// HTML 代码为
// <div id="divA">This is <span>some</span> text</div>
document.getElementById('divA').textContent
// This is some texttextContent属性自动忽略当前节点内部的 HTML 标签,返回所有文本内容。
该属性是可读写的,设置该属性的值,会用一个新的文本节点,替换所有原来的子节点。
它还有一个好处,就是自动对 HTML 标签转义(纯字符串使用)。这很适合用于用户提供的内容(防止用户插入带有标签的内容)。
document.getElementById('foo').textContent = '<p>GoodBye!</p>';上面代码在插入文本时,会将<p>标签解释为文本,而不会当作标签处理。
对于文本节点(text)、注释节点(comment)和属性节点(attr),textContent属性的值与nodeValue属性相同。
对于其他类型的节点,该属性会将每个子节点(不包括注释节点)的内容连接在一起返回。如果一个节点没有子节点,则返回空字符串。
文档节点(document)和文档类型节点(doctype)的textContent属性为null。如果要读取整个文档的内容,可以使用document.documentElement.textContent。
aseURI
Node.prototype.baseURI属性返回一个字符串,表示当前网页的绝对路径。浏览器根据这个属性,计算网页上的相对路径的 URL。该属性为只读。
// 当前网页的网址为
// http://www.example.com/index.html
document.baseURI
// "http://www.example.com/index.html"如果无法读到网页的 URL,baseURI属性返回null。
该属性的值一般由当前网址的 URL(即window.location属性)决定,但是可以使用 HTML 的<base>标签,改变该属性的值。
<base href="http://www.example.com/page.html">设置了以后,baseURI属性就返回<base>标签设置的值。
ownerDocument
Node.prototype.ownerDocument属性返回当前节点所在的顶层文档对象,即document对象。
document对象本身的ownerDocument属性,返回null。
nextSibling
Node.prototype.nextSibling 属性返回紧跟在当前节点后面的第一个同级节点。如果当前节点后面没有同级节点,则返回null。
// HTML 代码如下
// <div id="d1">hello</div><div id="d2">world</div>
var d1 = document.getElementById('d1');
var d2 = document.getElementById('d2');
d1.nextSibling === d2 // true上面代码中,d1.nextSibling就是紧跟在d1后面的同级节点d2。
注意,该属性还包括文本节点和注释节点(<!-- comment -->)。因此如果当前节点后面有空格,该属性会返回一个文本节点,内容为空格。
previousSibling
Node.prototype.previousSibling属性返回当前节点前面的、距离最近的一个同级节点。如果当前节点前面没有同级节点,则返回null。
注意,该属性还包括文本节点和注释节点。因此如果当前节点前面有空格,该属性会返回一个文本节点,内容为空格。
parentNode
Node.prototype.parentNode属性返回当前节点的父节点。对于一个节点来说,它的父节点只可能是三种类型:元素节点(element)、文档节点(document)和文档片段节点(documentfragment)。
文档节点(document)和文档片段节点(documentfragment)的父节点都是null。另外,对于那些生成后还没插入 DOM 树的节点,父节点也是null。
parentElement
Node.prototype.parentElement属性返回当前节点的父元素节点。如果当前节点没有父节点,或者父节点类型不是元素节点,则返回null。
由于父节点只可能是三种类型:元素节点、文档节点(document)和文档片段节点(documentfragment)。parentElement属性相当于把后两种父节点都排除了。
firstChild
Node.prototype.firstChild属性返回当前节点的第一个子节点,如果当前节点没有子节点,则返回null。
注意,firstChild返回的除了元素节点,还可能是文本节点或注释节点。
// HTML 代码如下
// <p id="p1">
// <span>First span</span>
// </p>
var p1 = document.getElementById('p1');
p1.firstChild.nodeName // "#text"上面代码中,p元素与span元素之间有空白字符,这导致firstChild返回的是文本节点。
lastChild
lastChild属性返回当前节点的最后一个子节点,如果当前节点没有子节点,则返回null。用法与firstChild属性相同。
childNodes
Node.prototype.childNodes属性返回一个类似数组的对象(NodeList集合),成员包括当前节点的所有子节点。
var children = document.querySelector('ul').childNodes;上面代码中,children就是ul元素的所有子节点。
使用该属性,可以遍历某个节点的所有子节点。
for (var i = 0; i < document.getElementById('div1').childNodes.length; i++) {
// ...
}文档节点(document)就有两个子节点:文档类型节点(docType)和 HTML 根元素节点。
#document/
├─ <! DOCTYPE html>
├─ <html>...</html>var children = document.childNodes;
for (var i = 0; i < children.length; i++) {
console.log(children[i].nodeType);
}
// 10
// 1上面代码中,文档节点的第一个子节点的类型是10(即文档类型节点),第二个子节点的类型是1(即元素节点)。
注意,除了元素节点,childNodes属性的返回值还包括文本节点和注释节点。
如果当前节点不包括任何子节点,则返回一个空的NodeList集合。由于NodeList对象是一个动态集合,一旦子节点发生变化,立刻会反映在返回结果之中。
isConnected
Node.prototype.isConnected属性返回一个布尔值,表示当前节点是否在文档之中。
var test = document.createElement('p');
test.isConnected // false
document.body.appendChild(test);
test.isConnected // true上面代码中,test节点是脚本生成的节点,没有插入文档之前,isConnected属性返回false,插入之后返回true。
Node 方法
appendChild()
Node.prototype.appendChild()方法接受一个节点对象作为参数,将其作为最后一个子节点,插入当前节点。该方法的返回值就是插入文档的子节点。
var p = document.createElement('p');
document.body.appendChild(p);上面代码新建一个<p>节点,将其插入document.body的尾部。
如果参数节点是 DOM 已经存在的节点,appendChild()方法会将其从原来的位置,移动到新位置。
如果appendChild()方法的参数是DocumentFragment节点,那么插入的是DocumentFragment的所有子节点,而不是DocumentFragment节点本身。返回值是一个空的DocumentFragment节点。
hasChildNodes()
Node.prototype.hasChildNodes()方法返回一个布尔值,表示当前节点是否有子节点。
注意,子节点包括所有类型的节点,并不仅仅是元素节点。哪怕节点只包含一个空格,hasChildNodes方法也会返回true。
判断一个节点有没有子节点,有许多种方法,下面是其中的三种。
node.hasChildNodes()node.firstChild !== nullnode.childNodes && node.childNodes.length > 0
cloneNode()
Node.prototype.cloneNode()方法用于克隆一个节点。它接受一个布尔值作为参数,表示是否同时克隆子节点。它的返回值是一个克隆出来的新节点。
var cloneUL = document.querySelector('ul').cloneNode(true);该方法有一些使用注意点。
(1)克隆一个节点,会拷贝该节点的所有属性,但是会丧失addEventListener方法和on-属性(即node.onclick = fn),添加在这个节点上的事件回调函数。
(2)该方法返回的节点不在文档之中,即没有任何父节点,必须使用诸如Node.appendChild这样的方法添加到文档之中。
(3)克隆一个节点之后,DOM 有可能出现两个有相同id属性(即id="xxx")的网页元素,这时应该修改其中一个元素的id属性。如果原节点有name属性,可能也需要修改。
insertBefore()
Node.prototype.insertBefore()方法用于将某个节点插入父节点内部的指定位置。
var insertedNode = parentNode.insertBefore(newNode, referenceNode);insertBefore方法接受两个参数,第一个参数是所要插入的节点newNode,第二个参数是父节点parentNode内部的一个子节点referenceNode。newNode将插在referenceNode这个子节点的前面。返回值是插入的新节点newNode。
var p = document.createElement('p');
document.body.insertBefore(p, document.body.firstChild);上面代码中,新建一个<p>节点,插在document.body.firstChild的前面,也就是成为document.body的第一个子节点。
如果insertBefore方法的第二个参数为null,则新节点将插在当前节点内部的最后位置,即变成最后一个子节点。
var p = document.createElement('p');
document.body.insertBefore(p, null);上面代码中,p将成为document.body的最后一个子节点。这也说明insertBefore的第二个参数不能省略。
注意,如果所要插入的节点是当前 DOM 现有的节点,则该节点将从原有的位置移除,插入新的位置。
由于不存在insertAfter方法,如果新节点要插在父节点的某个子节点后面,可以用insertBefore方法结合nextSibling属性模拟。
parent.insertBefore(s1, s2.nextSibling);上面代码中,parent是父节点,s1是一个全新的节点,s2是可以将s1节点,插在s2节点的后面。
如果s2是当前节点的最后一个子节点,则s2.nextSibling返回null,这时s1节点会插在当前节点的最后,变成当前节点的最后一个子节点,等于紧跟在s2的后面。
如果要插入的节点是DocumentFragment类型,那么插入的将是DocumentFragment的所有子节点,而不是DocumentFragment节点本身。返回值将是一个空的DocumentFragment节点。
removeChild()
Node.prototype.removeChild()方法接受一个子节点作为参数,用于从当前节点移除该子节点。返回值是移除的子节点。
var divA = document.getElementById('A');
divA.parentNode.removeChild(divA);上面代码移除了divA节点。注意,这个方法是在divA的父节点上调用的,不是在divA上调用的。
被移除的节点依然存在于内存之中,但不再是 DOM 的一部分。
所以,一个节点移除以后,依然可以使用它,比如插入到另一个节点下面。
如果参数节点不是当前节点的子节点,removeChild方法将报错。
replaceChild()
Node.prototype.replaceChild()方法用于将一个新的节点,替换当前节点的某一个子节点。
var replacedNode = parentNode.replaceChild(newChild, oldChild);上面代码中,replaceChild方法接受两个参数,第一个参数newChild是用来替换的新节点,第二个参数oldChild是将要替换走的子节点。返回值是替换走的那个节点oldChild。
例子
var divA = document.getElementById('divA');
var newSpan = document.createElement('span');
newSpan.textContent = 'Hello World!';
divA.parentNode.replaceChild(newSpan, divA);上面代码是如何将指定节点divA替换走。
contains()
Node.prototype.contains()方法返回一个布尔值,表示参数节点是否满足以下三个条件之一。
- 参数节点为当前节点。
- 参数节点为当前节点的子节点。
- 参数节点为当前节点的后代节点。
compareDocumentPosition()
Node.prototype.compareDocumentPosition()方法的用法,与contains方法完全一致,返回一个六个比特位的二进制值,表示参数节点与当前节点的关系。
| 二进制值 | 十进制值 | 含义 |
|---|---|---|
| 000000 | 0 | 两个节点相同 |
| 000001 | 1 | 两个节点不在同一个文档(即有一个节点不在当前文档) |
| 000010 | 2 | 参数节点在当前节点的前面 |
| 000100 | 4 | 参数节点在当前节点的后面 |
| 001000 | 8 | 参数节点包含当前节点 |
| 010000 | 16 | 当前节点包含参数节点 |
| 100000 | 32 | 浏览器内部使用 |
isEqualNode()
Node.prototype.isEqualNode()方法返回一个布尔值,用于检查两个节点是否相等。所谓相等的节点,指的是两个节点的类型相同、属性相同、子节点相同。
isSameNode()
Node.prototype.isSameNode()方法返回一个布尔值,表示两个节点是否为同一个节点。
normalize()
Node.prototype.normalize()方法用于清理当前节点内部的所有文本节点(text)。它会去除空的文本节点,并且将毗邻的文本节点合并成一个,也就是说不存在空的文本节点,以及毗邻的文本节点。
var wrapper = document.createElement('div');
wrapper.appendChild(document.createTextNode('Part 1 '));
wrapper.appendChild(document.createTextNode('Part 2 '));
wrapper.childNodes.length // 2
wrapper.normalize();
wrapper.childNodes.length // 1上面代码使用normalize方法之前,wrapper节点有两个毗邻的文本子节点。使用normalize方法之后,两个文本子节点被合并成一个。
该方法是Text.splitText的逆方法。
getRootNode()
Node.prototype.getRootNode()方法返回当前节点所在文档的根节点document,与ownerDocument属性的作用相同。
NodeList 接口,HTMLCollection 接口:
节点都是单个对象,有时需要一种数据结构,能够容纳多个节点。DOM 提供两种节点集合,用于容纳多个节点:NodeList和HTMLCollection。
这两种集合都属于接口规范。许多DOM属性和方法,返回的结果是NodeList实例或者HTMLCollection实例。
主要区别是,NodeList可以包含各种类型的节点,HTMLCollection只能包含HTML元素节点。
NodeList 接口
概述
NodeList实例是一个类似数组的对象,它的成员是节点对象。通过以下方法可以得到NodeList实例。
Node.childNodesdocument.querySelectorAll()等节点搜索方法
document.body.childNodes instanceof NodeList // trueNodeList实例很像数组,可以使用length属性和forEach方法。但是,它不是数组,不能使用pop或push之类数组特有的方法。
如果NodeList实例要使用数组方法,可以将其转为真正的数组。
var children = document.body.childNodes;
var nodeArr = Array.prototype.slice.call(children);除了使用forEach方法遍历 NodeList 实例,还可以使用for循环。
var children = document.body.childNodes;
for (var i = 0; i < children.length; i++) {
var item = children[i];
}注意,NodeList 实例可能是动态集合,也可能是静态集合。所谓动态集合就是一个活的集合,DOM 删除或新增一个相关节点,都会立刻反映在 NodeList 实例。
目前,只有Node.childNodes返回的是一个动态集合,其他的 NodeList 都是静态集合。
var children = document.body.childNodes;
children.length // 18
document.body.appendChild(document.createElement('p'));
children.length // 19上面代码中,文档增加一个子节点,NodeList 实例children的length属性就增加了1。
length
NodeList.prototype.length属性返回 NodeList 实例包含的节点数量。
forEach()
NodeList.prototype.forEach()方法用于遍历 NodeList 的所有成员。它接受一个回调函数作为参数,每一轮遍历就执行一次这个回调函数,用法与数组实例的forEach方法完全一致。
var children = document.body.childNodes;
children.forEach(function f(item, i, list) {
// ...
}, this);上面代码中,回调函数f的三个参数依次是当前成员、位置和当前 NodeList 实例。forEach方法的第二个参数,用于绑定回调函数内部的this,该参数可省略。
item()
NodeList.prototype.item()方法接受一个整数值作为参数,表示成员的位置,返回该位置上的成员。
document.body.childNodes.item(0)如果参数值大于实际长度,或者索引不合法(比如负数),item方法返回null。如果省略参数,item方法会报错。
所有类似数组的对象,都可以使用方括号运算符取出成员。一般情况下,都是使用方括号运算符,而不使用item方法。
document.body.childNodes[0]keys()
values()
entries()
这三个方法都返回一个 ES6 的遍历器对象,可以通过for...of循环遍历获取每一个成员的信息。
区别在于,keys()返回键名的遍历器,values()返回键值的遍历器,entries()返回的遍历器同时包含键名和键值的信息。
var children = document.body.childNodes;
for (var key of children.keys()) {
console.log(key);
}
// 0
// 1
// 2
// ...
for (var value of children.values()) {
console.log(value);
}
// #text
// <script>
// ...
for (var entry of children.entries()) {
console.log(entry);
}
// Array [ 0, #text ]
// Array [ 1, <script> ]
// ...HTMLCollection 接口
概述
HTMLCollection是一个节点对象的集合,只能包含元素节点(element),不能包含其他类型的节点。
它的返回值是一个类似数组的对象,但是与NodeList接口不同,HTMLCollection没有forEach方法,只能使用for循环遍历。
返回HTMLCollection实例的,主要是一些Document对象的集合属性,比如document.links、document.forms、document.images等。
document.links instanceof HTMLCollection // trueHTMLCollection实例都是动态集合,节点的变化会实时反映在集合中。
如果元素节点有id或name属性,那么HTMLCollection实例上面,可以使用id属性或name属性引用该节点元素。如果没有对应的节点,则返回null。
// HTML 代码如下
// <img id="pic" src="http://example.com/foo.jpg">
var pic = document.getElementById('pic');
document.images.pic === pic // true上面代码中,document.images是一个HTMLCollection实例,可以通过<img>元素的id属性值,从HTMLCollection实例上取到这个元素。
length()
HTMLCollection.prototype.length属性返回HTMLCollection实例包含的成员数量。
item()
HTMLCollection.prototype.item()方法接受一个整数值作为参数,表示成员的位置,返回该位置上的成员。
namedItem()
HTMLCollection.prototype.namedItem()方法的参数是一个字符串,表示id属性或name属性的值,返回当前集合中对应的元素节点。如果没有对应的节点,则返回null。
// HTML 代码如下
// <img id="pic" src="http://example.com/foo.jpg">
var pic = document.getElementById('pic');
document.images.namedItem('pic') === pic // trueParentNode 接口,ChildNode 接口:
节点对象除了继承 Node 接口以外,还拥有其他接口。
ParentNode接口表示当前节点是一个父节点,提供一些处理子节点的方法。ChildNode接口表示当前节点是一个子节点,提供一些相关方法。
ParentNode 接口
如果当前节点是父节点,就会混入了(mixin)ParentNode接口。
由于只有元素节点(element)、文档节点(document)和文档片段节点(documentFragment)拥有子节点,因此只有这三类节点会拥有ParentNode接口。
ParentNode.children
children属性返回一个HTMLCollection实例,成员是当前节点的所有元素子节点。该属性只读。
下面是遍历某个节点的所有元素子节点的示例。
for (var i = 0; i < el.children.length; i++) {
// ...
}注意,children属性只包括元素子节点,不包括其他类型的子节点(比如文本子节点)。如果没有元素类型的子节点,返回值HTMLCollection实例的length属性为0。
另外,HTMLCollection是动态集合,会实时反映 DOM 的任何变化。
ParentNode.firstElementChild
firstElementChild属性返回当前节点的第一个元素子节点。如果没有任何元素子节点,则返回null。
document.firstElementChild.nodeName
// "HTML"ParentNode.lastElementChild
lastElementChild属性返回当前节点的最后一个元素子节点,如果不存在任何元素子节点,则返回null。
ParentNode.childElementCount
childElementCount属性返回一个整数,表示当前节点的所有元素子节点的数目。如果不包含任何元素子节点,则返回0。
ParentNode.append()
append()方法为当前节点追加一个或多个子节点,位置是最后一个元素子节点的后面,该方法没有返回值。
该方法不仅可以添加元素子节点(参数为元素节点),还可以添加文本子节点(参数为字符串)。
var parent = document.body;
// 添加元素子节点
var p = document.createElement('p');
parent.append(p);
// 添加文本子节点
parent.append('Hello');
// 添加多个元素子节点
var p1 = document.createElement('p');
var p2 = document.createElement('p');
parent.append(p1, p2);
// 添加元素子节点和文本子节点
var p = document.createElement('p');
parent.append('Hello', p);注意,该方法与Node.prototype.appendChild()方法有三点不同。
append()允许字符串作为参数,appendChild()只允许子节点作为参数。append()没有返回值,而appendChild()返回添加的子节点。append()可以添加多个子节点和字符串(即允许多个参数),appendChild()只能添加一个节点(即只允许一个参数)。
ParentNode.prepend()
prepend()方法为当前节点追加一个或多个子节点,位置是第一个元素子节点的前面。它的用法与append()方法完全一致,也是没有返回值。
ChildNode 接口
如果一个节点有父节点,那么该节点就拥有了ChildNode接口。
ChildNode.remove()
remove()方法用于从父节点移除当前节点。
el.remove()上面代码在 DOM 里面移除了el节点。
ChildNode.before()
before()方法用于在当前节点的前面,插入一个或多个同级节点。两者拥有相同的父节点。
注意,该方法不仅可以插入元素节点,还可以插入文本节点。
var p = document.createElement('p');
var p1 = document.createElement('p');
// 插入元素节点
el.before(p);
// 插入文本节点
el.before('Hello');
// 插入多个元素节点
el.before(p, p1);
// 插入元素节点和文本节点
el.before(p, 'Hello');ChildNode.after()
after()方法用于在当前节点的后面,插入一个或多个同级节点,两者拥有相同的父节点。用法与before方法完全相同。
ChildNode.replaceWith()
replaceWith()方法使用参数节点,替换当前节点。参数可以是元素节点,也可以是文本节点。
var span = document.createElement('span');
el.replaceWith(span);上面代码中,el节点将被span节点替换。
Document 节点
概述
document节点对象代表整个文档,每张网页都有自己的document对象。
window.document属性就指向这个对象。只要浏览器开始载入 HTML 文档,该对象就存在了,可以直接使用。
document对象有不同的办法可以获取。
- 正常的网页,直接使用
document或window.document。 iframe框架里面的网页,使用iframe节点的contentDocument属性。- Ajax 操作返回的文档,使用
XMLHttpRequest对象的responseXML属性。 - 内部节点的
ownerDocument属性。
document对象继承了EventTarget接口和Node接口,并且混入(mixin)了ParentNode接口。
这意味着,这些接口的方法都可以在document对象上调用。除此之外,document对象还有很多自己的属性和方法。
快捷方式属性
以下属性是指向文档内部的某个节点的快捷方式。
(1)document.defaultView
document.defaultView属性返回document对象所属的window对象。如果当前文档不属于window对象,该属性返回null。
document.defaultView === window // true(2)document.doctype
对于 HTML 文档来说,document对象一般有两个子节点。第一个子节点是document.doctype,指向<DOCTYPE>节点,即文档类型(Document Type Declaration,简写DTD)节点。
HTML 的文档类型节点,一般写成<!DOCTYPE html>。如果网页没有声明 DTD,该属性返回null。
document.firstChild通常就返回这个节点。
(3)document.documentElement
document.documentElement属性返回当前文档的根元素节点(root)。
它通常是document节点的第二个子节点,紧跟在document.doctype节点后面。HTML网页的该属性,一般是<html>节点。
(4)document.body,document.head
document.body属性指向<body>节点,document.head属性指向<head>节点。
这两个属性总是存在的,如果网页源码里面省略了<head>或<body>,浏览器会自动创建。另外,这两个属性是可写的,如果改写它们的值,相当于移除所有子节点。
(5)document.scrollingElement
document.scrollingElement属性返回文档的滚动元素。也就是说,当文档整体滚动时,到底是哪个元素在滚动。
标准模式下,这个属性返回的文档的根元素document.documentElement(即<html>)。兼容(quirk)模式下,返回的是<body>元素,如果该元素不存在,返回null。
(6)document.activeElement
document.activeElement属性返回获得当前焦点(focus)的 DOM 元素。通常,这个属性返回的是<input>、<textarea>、<select>等表单元素,如果当前没有焦点元素,返回<body>元素或null。
(7)document.fullscreenElement
document.fullscreenElement属性返回当前以全屏状态展示的 DOM 元素。如果不是全屏状态,该属性返回null。
if (
document.fullscreenElement &&
document.fullscreenElement.nodeName == 'VIDEO'
) {
console.log('全屏播放视频');
}上面代码中,通过document.fullscreenElement可以知道<video>元素有没有处在全屏状态,从而判断用户行为。
节点集合属性
以下属性返回一个HTMLCollection实例,表示文档内部特定元素的集合。这些集合都是动态的,原节点有任何变化,立刻会反映在集合中。
(1)document.links
document.links属性返回当前文档所有设定了href属性的<a>及<area>节点。
(2)document.forms
document.forms属性返回所有<form>表单节点。
除了使用位置序号,id属性和name属性也可以用来引用表单。
document.forms[0] === document.forms.foo(3)document.images
document.images属性返回页面所有<img>图片节点。
var imglist = document.images;
for(var i = 0; i < imglist.length; i++) {
if (imglist[i].src === 'banner.gif') {
// ...
}
}上面代码在所有img标签中,寻找某张图片。
(4)document.embeds,document.plugins
document.embeds属性和document.plugins属性,都返回所有<embed>节点。
(5)document.scripts
document.scripts属性返回所有<script>节点。
var scripts = document.scripts;
if (scripts.length !== 0 ) {
console.log('当前网页有脚本');
}(6)document.styleSheets
document.styleSheets属性返回网页内嵌或引入的 CSS 样式表集合。
(7)小结
除了document.styleSheets属性,以上的其他集合属性返回的都是HTMLCollection实例。document.styleSheets属性返回的是StyleSheetList实例。
document.links instanceof HTMLCollection // true
document.images instanceof HTMLCollection // true
document.forms instanceof HTMLCollection // true
document.embeds instanceof HTMLCollection // true
document.scripts instanceof HTMLCollection // trueHTMLCollection实例是类似数组的对象,所以上面这些属性都有length属性,都可以使用方括号运算符引用成员。
如果成员有id或name属性,还可以用这两个属性的值,在HTMLCollection实例上引用到这个成员。
文档静态信息属性
以下属性返回文档信息。
(1)document.documentURI,document.URL
document.documentURI属性和document.URL属性都返回一个字符串,表示当前文档的网址(带锚点)。
不同之处是它们继承自不同的接口,
documentURI继承自Document接口,可用于所有文档;
URL继承自HTMLDocument接口,只能用于 HTML 文档。
如果文档的锚点(#anchor)变化,这两个属性都会跟着变化。
(2)document.domain
document.domain属性返回当前文档的域名,不包含协议和端口。
比如,网页的网址是http://www.example.com:80/hello.html,那么document.domain属性就等于www.example.com。如果无法获取域名,该属性返回null。
document.domain基本上是一个只读属性,只有一种情况除外。
次级域名的网页,可以把document.domain设为对应的上级域名。比如,当前域名是a.sub.example.com,则document.domain属性可以设置为sub.example.com,也可以设为example.com。修改后,document.domain相同的两个网页,可以读取对方的资源,比如设置的 Cookie。
另外,设置document.domain会导致端口被改成null。因此,如果通过设置document.domain来进行通信,双方网页都必须设置这个值,才能保证端口相同。
(3)document.location
Location对象是浏览器提供的原生对象,提供 URL 相关的信息和操作方法。通过window.location和document.location属性,可以拿到这个对象。
(4)document.lastModified
document.lastModified属性返回一个字符串,表示当前文档最后修改的时间。不同浏览器的返回值,日期格式是不一样的。
注意,document.lastModified属性的值是字符串,所以不能直接用来比较。Date.parse方法将其转为Date实例,才能比较两个网页。
var lastVisitedDate = Date.parse('01/01/2018');
if (Date.parse(document.lastModified) > lastVisitedDate) {
console.log('网页已经变更');
}如果页面上有 JavaScript 生成的内容,document.lastModified属性返回的总是当前时间。
(5)document.title
document.title属性返回当前文档的标题。默认情况下,返回<title>节点的值。但是该属性是可写的,一旦被修改,就返回修改后的值。
(6)document.characterSet
document.characterSet属性返回当前文档的编码,比如UTF-8、ISO-8859-1等等。
(7)document.referrer
document.referrer属性返回一个字符串,表示当前文档的访问者来自哪里。
如果无法获取来源,或者用户直接键入网址而不是从其他网页点击进入,document.referrer返回一个空字符串。
document.referrer的值,总是与 HTTP 头信息的Referer字段保持一致。但是,document.referrer的拼写有两个r,而头信息的Referer字段只有一个r。
(8)document.dir
document.dir返回一个字符串,表示文字方向。它只有两个可能的值:rtl表示文字从右到左,阿拉伯文是这种方式;ltr表示文字从左到右,包括英语和汉语在内的大多数文字采用这种方式。
(9)document.compatMode
compatMode属性返回浏览器处理文档的模式,可能的值为BackCompat(向后兼容模式)和CSS1Compat(严格模式)。
一般来说,如果网页代码的第一行设置了明确的DOCTYPE(比如<!doctype html>),document.compatMode的值都为CSS1Compat。
文档状态属性
(1)document.hidden
document.hidden属性返回一个布尔值,表示当前页面是否可见。如果窗口最小化、浏览器切换了 Tab,都会导致导致页面不可见,使得document.hidden返回true。
这个属性是 Page Visibility API 引入的,一般都是配合这个 API 使用。
(2)document.visibilityState
document.visibilityState返回文档的可见状态。
它的值有四种可能。
visible:页面可见。注意,页面可能是部分可见,即不是焦点窗口,前面被其他窗口部分挡住了。hidden:页面不可见,有可能窗口最小化,或者浏览器切换到了另一个 Tab。prerender:页面处于正在渲染状态,对于用户来说,该页面不可见。unloaded:页面从内存里面卸载了。
这个属性可以用在页面加载时,防止加载某些资源;或者页面不可见时,停掉一些页面功能。
(3)document.readyState
document.readyState属性返回当前文档的状态,共有三种可能的值。
loading:加载 HTML 代码阶段(尚未完成解析)interactive:加载外部资源阶段complete:加载完成
这个属性变化的过程如下。
- 浏览器开始解析 HTML 文档,
document.readyState属性等于loading。 - 浏览器遇到 HTML 文档中的
<script>元素,并且没有async或defer属性,就暂停解析,开始执行脚本,这时document.readyState属性还是等于loading。 - HTML 文档解析完成,
document.readyState属性变成interactive。 - 浏览器等待图片、样式表、字体文件等外部资源加载完成,一旦全部加载完成,
document.readyState属性变成complete。
下面的代码用来检查网页是否加载成功。
// 基本检查
if (document.readyState === 'complete') {
// ...
}
// 轮询检查
var interval = setInterval(function() {
if (document.readyState === 'complete') {
clearInterval(interval);
// ...
}
}, 100);另外,每次状态变化都会触发一个readystatechange事件。
document.cookie
document.cookie属性用来操作浏览器 Cookie。
document.designMode
document.designMode属性控制当前文档是否可编辑。该属性只有两个值on和off,默认值为off。一旦设为on,用户就可以编辑整个文档的内容。
下面代码打开iframe元素内部文档的designMode属性,就能将其变为一个所见即所得的编辑器。
// HTML 代码如下
// <iframe id="editor" src="about:blank"></iframe>
var editor = document.getElementById('editor');
editor.contentDocument.designMode = 'on';document.currentScript
document.currentScript属性只用在<script>元素的内嵌脚本或加载的外部脚本之中,返回当前脚本所在的那个 DOM 节点,即<script>元素的 DOM 节点。
<script id="foo">
console.log(
document.currentScript === document.getElementById('foo')
); // true
</script>上面代码中,document.currentScript就是<script>元素节点。
document.implementation
document.implementation属性返回一个DOMImplementation对象。该对象有三个方法,主要用于创建独立于当前文档的新的 Document 对象。
DOMImplementation.createDocument():创建一个 XML 文档。DOMImplementation.createHTMLDocument():创建一个 HTML 文档。DOMImplementation.createDocumentType():创建一个 DocumentType 对象。
var doc = document.implementation.createHTMLDocument('Title');
var p = doc.createElement('p');
p.innerHTML = 'hello world';
doc.body.appendChild(p);
document.replaceChild(
doc.documentElement,
document.documentElement
);上面代码中,第一步生成一个新的 HTML 文档doc,然后用它的根元素doc.documentElement替换掉document.documentElement。这会使得当前文档的内容全部消失,变成hello world。
document.open()
document.open方法清除当前文档所有内容,使得文档处于可写状态,供document.write方法写入内容。
document.close()
document.close方法用来关闭document.open()打开的文档。
document.open();
document.write('hello world');
document.close();document.write()
document.write方法用于向当前文档写入内容。
在网页的首次渲染阶段,只要页面没有关闭写入(即没有执行document.close()),document.write写入的内容就会追加在已有内容的后面。
注意,document.write会当作 HTML 代码解析,不会转义。
如果页面已经解析完成(DOMContentLoaded事件发生之后),再调用write方法,它会先调用open方法,擦除当前文档所有内容,然后再写入。
document.addEventListener('DOMContentLoaded', function (event) {
document.write('<p>Hello World!</p>');
});
// 等同于
document.addEventListener('DOMContentLoaded', function (event) {
document.open();
document.write('<p>Hello World!</p>');
document.close();
});如果在页面渲染过程中调用write方法,并不会自动调用open方法。(可以理解成,open方法已调用,但close方法还未调用。)
document.write是 JavaScript 语言标准化之前就存在的方法,现在完全有更符合标准的方法向文档写入内容(比如对innerHTML属性赋值)。所以,除了某些特殊情况,应该尽量避免使用document.write这个方法。
document.writeln()
document.writeln方法与write方法完全一致,除了会在输出内容的尾部添加换行符。
注意,writeln方法添加的是 ASCII 码的换行符,渲染成 HTML 网页时不起作用,即在网页上显示不出换行。网页上的换行,必须显式写入<br>。
document.querySelector()
document.querySelector方法接受一个 CSS 选择器作为参数,返回匹配该选择器的元素节点。如果有多个节点满足匹配条件,则返回第一个匹配的节点。如果没有发现匹配的节点,则返回null。
var el1 = document.querySelector('.myclass');document.querySelectorAll()
document.querySelectorAll方法与querySelector用法类似,区别是返回一个NodeList对象,包含所有匹配给定选择器的节点。
elementList = document.querySelectorAll('.myclass');这两个方法的参数,可以是逗号分隔的多个 CSS 选择器,返回匹配其中一个选择器的元素节点,这与 CSS 选择器的规则是一致的。
var matches = document.querySelectorAll('div.note, div.alert');上面代码返回class属性是note或alert的div元素。
上述的两个方法都支持复杂的 CSS 选择器。
// 选中 data-foo-bar 属性等于 someval 的元素
document.querySelectorAll('[data-foo-bar="someval"]');
// 选中 myForm 表单中所有不通过验证的元素
document.querySelectorAll('#myForm :invalid');
// 选中div元素,那些 class 含 ignore 的除外
document.querySelectorAll('DIV:not(.ignore)');
// 同时选中 div,a,script 三类元素
document.querySelectorAll('DIV, A, SCRIPT');但是,它们不支持 CSS 伪元素的选择器(比如:first-line和:first-letter)和伪类的选择器(比如:link和:visited),即无法选中伪元素和伪类。
如果querySelectorAll方法的参数是字符串*,则会返回文档中的所有元素节点。
另外,querySelectorAll的返回结果不是动态集合,不会实时反映元素节点的变化。
最后,上述两个方法除了定义在document对象上,还定义在元素节点上,即在元素节点上也可以调用。
document.getElementsByTagName()
document.getElementsByTagName()方法搜索 HTML 标签名,返回符合条件的元素。
它的返回值是一个类似数组对象(HTMLCollection实例),可以实时反映 HTML 文档的变化。如果没有任何匹配的元素,就返回一个空集。
var paras = document.getElementsByTagName('p');
paras instanceof HTMLCollection // true上面代码返回当前文档的所有p元素节点。
HTML 标签名是大小写不敏感的,因此getElementsByTagName()方法的参数也是大小写不敏感的。
返回结果中,各个成员的顺序就是它们在文档中出现的顺序。
如果传入*,就可以返回文档中所有 HTML 元素。
var allElements = document.getElementsByTagName('*');注意,元素节点本身也定义了getElementsByTagName方法,返回该元素的后代元素中符合条件的元素。也就是说,这个方法不仅可以在document对象上调用,也可以在任何元素节点上调用。
var firstPara = document.getElementsByTagName('p')[0];
var spans = firstPara.getElementsByTagName('span');上面代码选中第一个p元素内部的所有span元素。
document.getElementsByClassName()
document.getElementsByClassName()方法返回一个类似数组的对象(HTMLCollection实例),包括了所有class名字符合指定条件的元素,元素的变化实时反映在返回结果中。
参数可以是多个class,它们之间使用空格分隔。
var elements = document.getElementsByClassName('foo bar');上面代码返回同时具有foo和bar两个class的元素,foo和bar的顺序不重要。
注意,正常模式下,CSS 的class是大小写敏感的。(quirks mode下,大小写不敏感。)
与getElementsByTagName()方法一样,getElementsByClassName()方法不仅可以在document对象上调用,也可以在任何元素节点上调用。
document.getElementsByName()
document.getElementsByName()方法用于选择拥有name属性的 HTML 元素(比如<form>、<radio>、<img>、<frame>、<embed>和<object>等),返回一个类似数组的的对象(NodeList实例),因为name属性相同的元素可能不止一个。
// 表单为 <form name="x"></form>
var forms = document.getElementsByName('x');
forms[0].tagName // "FORM"document.getElementById()
document.getElementById()方法返回匹配指定id属性的元素节点。如果没有发现匹配的节点,则返回null。
注意,该方法的参数是大小写敏感的。比如,如果某个节点的id属性是main,那么document.getElementById('Main')将返回null。
document.getElementById()方法与document.querySelector()方法都能获取元素节点,不同之处是document.querySelector()方法的参数使用 CSS 选择器语法,document.getElementById()方法的参数是元素的id属性。
document.getElementById('myElement')
document.querySelector('#myElement')上面代码中,两个方法都能选中id为myElement的元素,但是document.getElementById()比document.querySelector()效率高得多。
另外,这个方法(document.getElementById())只能在document对象上使用,不能在其他元素节点上使用。
document.elementFromPoint()
document.elementFromPoint()方法返回位于页面指定位置(坐标)最上层的元素节点。
var element = document.elementFromPoint(50, 50);上面代码选中在(50, 50)这个坐标位置的最上层的那个 HTML 元素。
elementFromPoint方法的两个参数,依次是相对于当前视口左上角的横坐标和纵坐标,单位是像素。
如果位于该位置的 HTML 元素不可返回(比如文本框的滚动条),则返回它的父元素(比如文本框)。如果坐标值无意义(比如负值或超过视口大小),则返回null。
document.elementsFromPoint()
document.elementsFromPoint()返回一个数组,成员是位于指定坐标(相对于视口)的所有元素。
document.createElement()
document.createElement方法用来生成元素节点,并返回该节点。
var newDiv = document.createElement('div');createElement方法的参数为元素的标签名,即元素节点的tagName属性,对于 HTML 网页大小写不敏感,即参数为div或DIV返回的是同一种节点。如果参数里面包含尖括号(即<和>)会报错。
注意,document.createElement的参数可以是自定义的标签名。
document.createTextNode()
document.createTextNode方法用来生成文本节点(Text实例),并返回该节点。它的参数是文本节点的内容。
var newDiv = document.createElement('div');
var newContent = document.createTextNode('Hello');
newDiv.appendChild(newContent);上面代码新建一个div节点和一个文本节点,然后将文本节点插入div节点。
这个方法可以确保返回的节点,被浏览器当作文本渲染,而不是当作 HTML 代码渲染。因此,可以用来展示用户的输入,避免 XSS 攻击。
var div = document.createElement('div');
div.appendChild(document.createTextNode('<span>Foo & bar</span>'));
console.log(div.innerHTML)
// <span>Foo & bar</span>上面代码中,createTextNode方法对大于号和小于号进行转义,从而保证即使用户输入的内容包含恶意代码,也能正确显示。
需要注意的是,该方法不对单引号和双引号转义,所以不能用来对 HTML 属性赋值。
function escapeHtml(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
};
var userWebsite = '" onmouseover="alert(\'derp\')" "';
var profileLink = '<a href="' + escapeHtml(userWebsite) + '">Bob</a>';
var div = document.getElementById('target');
div.innerHTML = profileLink;
// <a href="" onmouseover="alert('derp')" "">Bob</a>上面代码中,由于createTextNode方法不转义双引号,导致onmouseover方法被注入了代码。
document.createAttribute()
document.createAttribute方法生成一个新的属性节点(Attr实例),并返回它。
var attribute = document.createAttribute(name);var node = document.getElementById('div1');
var a = document.createAttribute('my_attrib');
a.value = 'newVal';
node.setAttributeNode(a);
// 或者
node.setAttribute('my_attrib', 'newVal');document.createAttribute方法的参数 name,是属性的名称。
document.createComment()
document.createComment方法生成一个新的注释节点,并返回该节点。
var CommentNode = document.createComment(data);document.createComment方法的参数是一个字符串,会成为注释节点的内容。
document.createDocumentFragment()
document.createDocumentFragment方法生成一个空的文档片段对象(DocumentFragment实例)。
var docFragment = document.createDocumentFragment();DocumentFragment是一个存在于内存的 DOM 片段,不属于当前文档,常常用来生成一段较复杂的 DOM 结构,然后再插入当前文档。
这样做的好处在于,因为 DocumentFragment不属于当前文档,对它的任何改动,都不会引发网页的重新渲染,比直接修改当前文档的 DOM 有更好的性能表现。
var docfrag = document.createDocumentFragment();
[1, 2, 3, 4].forEach(function (e) {
var li = document.createElement('li');
li.textContent = e;
docfrag.appendChild(li);
});
var element = document.getElementById('ul');
element.appendChild(docfrag);上面代码中,文档片段 docfrag包含四个 <li>节点,这些子节点被一次性插入了当前文档。
document.createEvent()
document.createEvent方法生成一个事件对象(Event实例),该对象可以被 element.dispatchEvent方法使用,触发指定事件。
var event = document.createEvent(type);document.createEvent方法的参数是事件类型,比如 UIEvents、MouseEvents、MutationEvents、HTMLEvents。
var event = document.createEvent('Event');
event.initEvent('build', true, true);
document.addEventListener('build', function (e) {
console.log(e.type); // "build"
}, false);
document.dispatchEvent(event);上面代码新建了一个名为 build的事件实例,然后触发该事件。
document.addEventListener()
document.removeEventListener()
document.dispatchEvent()
这三个方法用于处理 document节点的事件。它们都继承自 EventTarget接口。
// 添加事件监听函数
document.addEventListener('click', listener, false);
// 移除事件监听函数
document.removeEventListener('click', listener, false);
// 触发事件
var event = new Event('click');
document.dispatchEvent(event);document.hasFocus()
document.hasFocus方法返回一个布尔值,表示当前文档之中是否有元素被激活或获得焦点。
var focused = document.hasFocus();注意,有焦点的文档必定被激活(active),反之不成立,激活的文档未必有焦点。比如,用户点击按钮,从当前窗口跳出一个新窗口,该新窗口就是激活的,但是不拥有焦点。
document.adoptNode()
document.adoptNode方法将某个节点及其子节点,从原来所在的文档或DocumentFragment里面移除,归属当前document对象,返回插入后的新节点。
插入的节点对象的ownerDocument属性,会变成当前的document对象,而parentNode属性是null。
var node = document.adoptNode(externalNode);
document.appendChild(node);注意,document.adoptNode方法只是改变了节点的归属,并没有将这个节点插入新的文档树。所以,还要再用appendChild方法或insertBefore方法,将新节点插入当前文档树。
document.importNode()
document.importNode方法则是从原来所在的文档或DocumentFragment里面,拷贝某个节点及其子节点,让它们归属当前document对象。
拷贝的节点对象的ownerDocument属性,会变成当前的document对象,而parentNode属性是null。
var node = document.importNode(externalNode, deep);document.importNode方法的第一个参数是外部节点,第二个参数是一个布尔值,表示对外部节点是深拷贝还是浅拷贝,默认是浅拷贝(false)。虽然第二个参数是可选的,但是建议总是保留这个参数,并设为true。
注意,document.importNode方法只是拷贝外部节点,这时该节点的父节点是null。下一步还必须将这个节点插入当前文档树。
var iframe = document.getElementsByTagName('iframe')[0];
var oldNode = iframe.contentWindow.document.getElementById('myNode');
var newNode = document.importNode(oldNode, true);
document.getElementById("container").appendChild(newNode);上面代码从iframe窗口,拷贝一个指定节点myNode,插入当前文档。
document.createNodeIterator()
document.createNodeIterator方法返回一个子节点遍历器。
var nodeIterator = document.createNodeIterator(
document.body,
NodeFilter.SHOW_ELEMENT
);上面代码返回<body>元素子节点的遍历器。
document.createNodeIterator方法第一个参数为所要遍历的根节点,第二个参数为所要遍历的节点类型,这里指定为元素节点(NodeFilter.SHOW_ELEMENT)。几种主要的节点类型写法如下。
- 所有节点:NodeFilter.SHOW_ALL
- 元素节点:NodeFilter.SHOW_ELEMENT
- 文本节点:NodeFilter.SHOW_TEXT
- 评论节点:NodeFilter.SHOW_COMMENT
document.createNodeIterator方法返回一个“遍历器”对象(NodeFilter实例)。该实例的nextNode()方法和previousNode()方法,可以用来遍历所有子节点。
var nodeIterator = document.createNodeIterator(document.body);
var pars = [];
var currentNode;
while (currentNode = nodeIterator.nextNode()) {
pars.push(currentNode);
}上面代码中,使用遍历器的nextNode方法,将根节点的所有子节点,依次读入一个数组。nextNode方法先返回遍历器的内部指针所在的节点,然后会将指针移向下一个节点。所有成员遍历完成后,返回null。previousNode方法则是先将指针移向上一个节点,然后返回该节点。
var nodeIterator = document.createNodeIterator(
document.body,
NodeFilter.SHOW_ELEMENT
);
var currentNode = nodeIterator.nextNode();
var previousNode = nodeIterator.previousNode();
currentNode === previousNode // true上面代码中,currentNode和previousNode都指向同一个的节点。
注意,遍历器返回的第一个节点,总是根节点。
pars[0] === document.body // truedocument.createTreeWalker()
document.createTreeWalker方法返回一个 DOM 的子树遍历器。它与document.createNodeIterator方法基本是类似的,区别在于它返回的是TreeWalker实例,后者返回的是NodeIterator实例。另外,它的第一个节点不是根节点。
document.createTreeWalker方法的第一个参数是所要遍历的根节点,第二个参数指定所要遍历的节点类型(与document.createNodeIterator方法的第二个参数相同)。
var treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ELEMENT
);
var nodeList = [];
while(treeWalker.nextNode()) {
nodeList.push(treeWalker.currentNode);
}上面代码遍历<body>节点下属的所有元素节点,将它们插入nodeList数组。
document.execCommand()
如果document.designMode属性设为on,那么整个文档用户可编辑;如果元素的contenteditable属性设为true,那么该元素可编辑。这两种情况下,可以使用document.execCommand()方法,改变内容的样式,比如document.execCommand('bold')会使得字体加粗。
document.execCommand(command, showDefaultUI, input)该方法接受三个参数。
command:字符串,表示所要实施的样式。showDefaultUI:布尔值,表示是否要使用默认的用户界面,建议总是设为false。input:字符串,表示该样式的辅助内容,比如生成超级链接时,这个参数就是所要链接的网址。如果第二个参数设为true,那么浏览器会弹出提示框,要求用户在提示框输入该参数。但是,不是所有浏览器都支持这样做,为了兼容性,还是需要自己部署获取这个参数的方式。
var url = window.prompt('请输入网址');
if (url) {
document.execCommand('createlink', false, url);
}上面代码中,先提示用户输入所要链接的网址,然后手动生成超级链接。注意,第二个参数是false,表示此时不需要自动弹出提示框。
document.execCommand()的返回值是一个布尔值。如果为false,表示这个方法无法生效。
这个方法大部分情况下,只对选中的内容生效。如果有多个内容可编辑区域,那么只对当前焦点所在的元素生效。
document.execCommand()方法可以执行的样式改变有很多种,下面是其中的一些:bold、insertLineBreak、selectAll、createLink、insertOrderedList、subscript、delete、insertUnorderedList、superscript、formatBlock、insertParagraph、undo、forwardDelete、insertText、unlink、insertImage、italic、unselect、insertHTML、redo。这些值都可以用作第一个参数,它们的含义不难从字面上看出来。
document.queryCommandSupported()
document.queryCommandSupported()方法返回一个布尔值,表示浏览器是否支持document.execCommand()的某个命令。
if (document.queryCommandSupported('SelectAll')) {
console.log('浏览器支持选中可编辑区域的所有内容');
}document.queryCommandEnabled()
document.queryCommandEnabled()方法返回一个布尔值,表示当前是否可用document.execCommand()的某个命令。比如,bold(加粗)命令只有存在文本选中时才可用,如果没有选中文本,就不可用。
// HTML 代码为
// <input type="button" value="Copy" onclick ="doCopy()">
function doCopy(){
// 浏览器是否支持 copy 命令(选中内容复制到剪贴板)
if (document.queryCommandSupported('copy')) {
copyText('你好');
}else{
console.log('浏览器不支持');
}
}
function copyText(text) {
var input = document.createElement('textarea');
document.body.appendChild(input);
input.value = text;
input.focus();
input.select();
// 当前是否有选中文字
if (document.queryCommandEnabled('copy')) {
var success = document.execCommand('copy');
input.remove();
console.log('Copy Ok');
} else {
console.log('queryCommandEnabled is false');
}
}上面代码中,先判断浏览器是否支持copy命令(允许可编辑区域的选中内容,复制到剪贴板),如果支持,就新建一个临时文本框,里面写入内容“你好”,并将其选中。然后,判断是否选中成功,如果成功,就将“你好”复制到剪贴板,再删除那个临时文本框。
document.getSelection()
这个方法指向window.getSelection()