目录
1 DOM的概念
2 节点的概念
3 Node节点的属性
3.1 nodeName,nodeType
3.2 ownerDocument,nextSibling,previousSibling,parentNode,parentElement
3.2.1 ownerDocument
3.2.2 nextSibling
3.2.3 previousSibling
3.2.4 parentNode
3.2.5 parentElement
3.3 textContent,nodeValue
3.3.1 textContent
3.3.2 nodeValue
3.4 childNodes,firstNode,lastChild
3.4.1 childNodes
3.4.2 firstNode
3.4.3 lastChild
3.5 baseURI
4 Node节点的方法
4.1 appendChild(),hasChildNodes()
4.1.1 appendChild()
4.1.2 hasChildNodes()
4.2 cloneNode(),insertBefore(),removeChild(),replaceChild()
4.2.1 cloneNode()
4.2.2 insertBefore()
4.2.3 removeChild()
4.2.4 replaceChild()
4.3 contains(),compareDocumentPosition(),isEqualNode()
4.3.1 contains()
4.3.2 compareDocumentPosition()
4.3.3 isEqualNode()
4.4 normalize()
DOM是文档对象模型(Document Object Model)的简称,它的基本思想是把结构化文档(比如HTML和XML)解析成一系列的节点,再由这些节点组成一个树状结构(DOM Tree)。所有的节点和最终的树状结构,都有规范的对外接口,以达到使用编程语言操作文档的目的(比如增删内容)。所以,DOM可以理解成文档(HTML文档、XML文档和SVG文档)的编程接口。
严格地说,DOM不属于JavaScript,但是操作DOM是JavaScript最常见的任务,而JavaScript也是最常用于DOM操作的语言。所以,DOM往往放在JavaScript里面介绍。
DOM的最小组成单位叫做节点(node),一个文档的树形结构(DOM树),就是由各种不同类型的节点组成。
对于HTML文档,节点主要有以下六种类型:Document节点、DocumentType节点、Element节点、Attribute节点、Text节点和DocumentFragment节点。
节点 | 名称 | 含义 |
---|---|---|
Document | 文档节点 | 整个文档(window.document) |
DocumentType | 文档类型节点 | 文档的类型(比如) |
Element | 元素节点 | HTML元素(比如、等) |
Attribute | 属性节点 | HTML元素的属性(比如class="right") |
Text | 文本节点 | HTML文档中出现的文本 |
DocumentFragment | 文档碎片节点 | 文档的片段 |
浏览器原生提供一个Node对象,上表所有类型的节点都是Node对象派生出来的。也就是说,它们都继承了Node的属性和方法。
nodeName属性返回节点的名称,nodeType属性返回节点的常数值。具体的返回值,可查阅下方的表格。
类型 | nodeName | nodeType |
---|---|---|
DOCUMENT_NODE | #document | 9 |
ELEMENT_NODE | 大写的HTML元素名 | 1 |
ATTRIBUTE_NODE | 等同于Attr.name | 2 |
TEXT_NODE | #text | 3 |
DOCUMENT_FRAGMENT_NODE | #document-fragment | 11 |
DOCUMENT_TYPE_NODE | 等同于DocumentType.name | 10 |
以document
节点为例,它的nodeName
属性等于#document
,nodeType
属性等于9。
document.nodeName // "#document"
document.nodeType // 9
通常来说,使用nodeType
属性确定一个节点的类型,比较方便。
document.querySelector('a').nodeType === 1
// true
document.querySelector('a').nodeType === Node.ELEMENT_NODE
// true
上面两种写法是等价的。
以下属性返回当前节点的相关节点
ownerDocument属性返回当前节点所在的顶层文档对象,即document对象
var d = p.ownerDocument;
d === document // true
document对象本身的ownerDocument属性,返回null。
nextSibling属性返回紧跟在当前节点后面的第一个同级节点。如果当前节点后面没有同级节点,则返回null。
注意,该属性还包括文本节点和评论节点。因此如果当前节点后面有空格,该属性会返回一个文本节点,内容为空格。
var el = document.getElementById('div-01').firstChild;
var i = 1;
while (el) {
console.log(i + '. ' + el.nodeName);
el = el.nextSibling;
i++;
}
上面代码遍历div-01
节点的所有子节点。
previousSibling属性返回当前节点前面的、距离最近的一个同级节点。如果当前节点前面没有同级节点,则返回null。
// html代码如下
//
document.getElementById("b1").previousSibling // null
document.getElementById("b2").previousSibling.id // "b1"
对于当前节点前面有空格,则previoussibling属性会返回一个内容为空格的文本节点。
parentNode属性返回当前节点的父节点。对于一个节点来说,它的父节点只可能是三种类型:element节点、document节点和documentfragment节点。
下面代码是如何从父节点移除指定节点。
if (node.parentNode) {
node.parentNode.removeChild(node);
}
对于document节点和documentfragment节点,它们的父节点都是null。另外,对于那些生成后还没插入DOM树的节点,父节点也是null。
parentElement属性返回当前节点的父Element节点。如果当前节点没有父节点,或者父节点类型不是Element节点,则返回null。
if (node.parentElement) {
node.parentElement.style.color = "red";
}
上面代码设置指定节点的父Element节点的CSS属性。
在IE浏览器中,只有Element节点才有该属性,其他浏览器则是所有类型的节点都有该属性。
以下属性返回当前节点的内容
textContent属性返回当前节点和它的所有后代节点的文本内容。
// HTML代码为
// This is some text
document.getElementById("divA").textContent
// This is some text
上面代码的textContent属性,自动忽略当前节点内部的HTML标签,返回所有文本内容。
该属性是可读写的,设置该属性的值,会用一个新的文本节点,替换所有它原来的子节点。它还有一个好处,就是自动对HTML标签转义。这很适合用于用户提供的内容。
document.getElementById('foo').textContent = 'GoodBye!
';
上面代码在插入文本时,会将p标签解释为文本,即<p>,而不会当作标签处理。
对于Text节点和Comment节点,该属性的值与nodeValue属性相同。对于其他类型的节点,该属性会将每个子节点的内容连接在一起返回,但是不包括Comment节点。如果一个节点没有子节点,则返回空字符串。
document节点和doctype节点的textContent属性为null。如果要读取整个文档的内容,可以使用document.documentElement.textContent
。
在IE浏览器,所有Element节点都有一个innerText属性。它与textContent属性基本相同,但是有几点区别:
nodeValue属性返回或设置当前节点的值,格式为字符串。但是,该属性只对Text节点、Comment节点、XML文档的CDATA节点有效,其他类型的节点一律返回null。
因此,nodeValue属性一般只用于Text节点。对于那些返回null的节点,设置nodeValue属性是无效的。
以下属性返回当前节点的子节点。
childNodes属性返回一个NodeList集合,成员包括当前节点的所有子节点。
注意:除了HTML元素节点,该属性返回的还包括Text节点和Comment节点。如果当前节点不包括任何子节点,则返回一个空的NodeList集合。由于NodeList对象是一个动态集合,一旦子节点发生变化,立刻会反映在返回结果之中。
var ulElementChildNodes = document.querySelector('ul').childNodes;
firstNode属性返回当前节点的第一个子节点,如果当前节点没有子节点,则返回null。注意,除了HTML元素子节点,该属性还包括文本节点和评论节点。
lastChild属性返回当前节点的最后一个子节点,如果当前节点没有子节点,则返回null。
baseURI属性返回一个字符串,由当前网页的协议、域名和所在的目录组成,表示当前网页的绝对路径。如果无法取到这个值,则返回null。浏览器根据这个属性,计算网页上的相对路径的URL。该属性为只读。
通常情况下,该属性由当前网址的URL(即window.location属性)决定,但是可以使用HTML的
该属性不仅document对象有(document.baseURI
),元素节点也有(element.baseURI
)。通常情况下,它们的值是相同的。
appendChild方法接受一个节点对象作为参数,将其作为最后一个子节点,插入当前节点。
var p = document.createElement("p");
document.body.appendChild(p);
如果参数节点是文档中现有的其他节点,appendChild方法会将其从原来的位置,移动到新位置。
hasChildNodes方法返回一个布尔值,表示当前节点是否有子节点。
var foo = document.getElementById("foo");
if ( foo.hasChildNodes() ) {
foo.removeChild( foo.childNodes[0] );
}
上面代码表示,如果foo节点有子节点,就移除第一个子节点。
hasChildNodes方法结合firstChild属性和nextSibling属性,可以遍历当前节点的所有后代节点。
function DOMComb (oParent, oCallback) {
if (oParent.hasChildNodes()) {
for (var oNode = oParent.firstChild; oNode; oNode = oNode.nextSibling) {
DOMComb(oNode, oCallback);
}
}
oCallback.call(oParent);
}
上面代码的DOMComb函数的第一个参数是某个指定的节点,第二个参数是回调函数。这个回调函数会依次作用于指定节点,以及指定节点的所有后代节点。
function printContent () {
if (this.nodeValue) {
console.log(this.nodeValue);
}
}
DOMComb(document.body, printContent);
下面方法与节点操作有关。
cloneNode方法用于克隆一个节点。它接受一个布尔值作为参数,表示是否同时克隆子节点,默认是false,即不克隆子节点
var cloneUL = document.querySelector('ul').cloneNode(true);
需要注意的是,克隆一个节点,会拷贝该节点的所有属性,但是会丧失addEventListener方法和on-属性(即node.onclick = fn
),添加在这个节点上的事件回调函数。
克隆一个节点之后,DOM树有可能出现两个有相同ID属性(即id="xxx"
)的HTML元素,这时应该修改其中一个HTML元素的ID属性。
insertBefore方法用于将某个节点插入当前节点的指定位置。它接受两个参数,第一个参数是所要插入的节点,第二个参数是当前节点的一个子节点,新的节点将插在这个节点的前面。该方法返回被插入的新节点。
var text1 = document.createTextNode('1');
var li = document.createElement('li');
li.appendChild(text1);
var ul = document.querySelector('ul');
ul.insertBefore(li,ul.firstChild);
上面代码在ul节点的最前面,插入一个新建的li节点。
如果insertBefore方法的第二个参数为null,则新节点将插在当前节点的最后位置,即变成最后一个子节点。
将新节点插在当前节点的最前面(即变成第一个子节点),可以使用当前节点的firstChild属性。
parentElement.insertBefore(newElement, parentElement.firstChild);
上面代码中,如果当前节点没有任何子节点,parentElement.firstChild
会返回null,则新节点会插在当前节点的最后,等于是第一个子节点。
由于不存在insertAfter方法,如果要插在当前节点的某个子节点后面,可以用insertBefore方法结合nextSibling属性模拟。
parentDiv.insertBefore(s1, s2.nextSibling);
上面代码可以将s1节点,插在s2节点的后面。如果s2是当前节点的最后一个子节点,则s2.nextSibling
返回null,这时s1节点会插在当前节点的最后,变成当前节点的最后一个子节点,等于紧跟在s2的后面。
removeChild方法接受一个子节点作为参数,用于从当前节点移除该节点。它返回被移除的节点
var divA = document.getElementById('A');
divA.parentNode.removeChild(divA);
上面代码是如何移除一个指定节点。
下面是如何移除当前节点的所有子节点。
var element = document.getElementById("top");
while (element.firstChild) {
element.removeChild(element.firstChild);
}
被移除的节点依然存在于内存之中,但是不再是DOM的一部分。所以,一个节点移除以后,依然可以使用它,比如插入到另一个节点。
replaceChild方法用于将一个新的节点,替换当前节点的某一个子节点。它接受两个参数,第一个参数是用来替换的新节点,第二个参数将要被替换走的子节点。它返回被替换走的那个节点。
replacedNode = parentNode.replaceChild(newChild, oldChild);
下面是一个例子。
var divA = document.getElementById('A');
var newSpan = document.createElement('span');
newSpan.textContent = 'Hello World!';
divA.parentNode.replaceChild(newSpan,divA);
上面代码是如何替换指定节点。
下面方法用于节点的互相比较
contains方法接受一个节点作为参数,返回一个布尔值,表示参数节点是否为当前节点的后代节点
document.body.contains(node)
上面代码检查某个节点,是否包含在当前文档之中。
注意,如果将当前节点传入contains方法,会返回true。虽然从意义上说,一个节点不应该包含自身。
nodeA.contains(nodeA) // true
compareDocumentPosition方法的用法,与contains方法完全一致,返回一个7个比特位的二进制值,表示参数节点与当前节点的关系
二进制值 | 数值 | 含义 |
---|---|---|
000000 | 0 | 两个节点相同 |
000001 | 1 | 两个节点不在同一个文档(即有一个节点不在当前文档) |
000010 | 2 | 参数节点在当前节点的前面 |
000100 | 4 | 参数节点在当前节点的后面 |
001000 | 8 | 参数节点包含当前节点 |
010000 | 16 | 当前节点包含参数节点 |
100000 | 32 | 浏览器的私有用途 |
// HTML代码为
//
//
//
var x = document.getElementById('writeroot');
var y = document.getElementById('test');
x.compareDocumentPosition(y) // 20
y.compareDocumentPosition(x) // 10
上面代码中,节点x包含节点y,而且节点y在节点x的后面,所以第一个compareDocumentPosition方法返回20(010100),第二个compareDocumentPosition方法返回10(0010010)。
由于compareDocumentPosition返回值的含义,定义在每一个比特位上,所以如果要检查某一种特定的含义,就需要使用比特位运算符。
var head = document.head;
var body = document.body;
if (head.compareDocumentPosition(body) & 4) {
console.log("文档结构正确");
} else {
console.log(" 不能在 前面");
}
上面代码中,compareDocumentPosition的返回值与4(又称掩码)进行与运算(&),得到一个布尔值,表示head是否在body前面。
在这个方法的基础上,可以部署一些特定的函数,检查节点的位置。
Node.prototype.before = function (arg) {
return !!(this.compareDocumentPosition(arg) & 2)
}
nodeA.before(nodeB)
上面代码在Node对象上部署了一个before方法,返回一个布尔值,表示参数节点是否在当前节点的前面。
isEqualNode方法返回一个布尔值,用于检查两个节点是否相等。所谓相等的节点,指的是两个节点的类型相同、属性相同、子节点相同。
var targetEl = document.getElementById("targetEl");
var firstDiv = document.getElementsByTagName("div")[0];
targetEl.isEqualNode(firstDiv)
normailize方法用于清理当前节点内部的所有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节点有两个Text子节点。使用normalize方法之后,两个Text子节点被合并成一个。