几个概念:
DOM:文档对象模型,是针对 HTML 和 XML 文档的一个 API(应用程序编程接口)。
根节点:就是 Document 节点。
nodeType: 所有节点的属性,用于表明节点的类型。
Node 类型
DOM1 级的接口,该接口除了 IE 之外,在其他所有浏览器中都可以访问到这个类型。
节点类型由在 Node 类型中定义的下列 12 个数值常量来表示,任何节点类型必居其一:
Node.ELEMENT_NODE;
Node.ATTRIBUTE_NODE;
Node.TEXT_NODE;
Node.CDATA_SECTION_NODE;
Node.ENTITY_REFERENCE_NODE;
Node.ENTITY_NODE;
Node.PROCESSING_INSTRUCTION_NODE;
Node.COMMENT_NODE;
Node.DOCUMENT_NODE;
Node.DOCUMENT_TYPE_NODE;
11. Node.DOCUMENT_FRAGMENT_NODE;
12. Node.NOTATION_NODE;
分别返回 1-12 数值。
如:
hello this is h1
hello this is h2
this is a paragraph
console.log(document.getElementById("h1").nodeType == Node.ELEMENT_NODE); //true
因为 IE 兼容性问题,Node 类型的构造函数无效,最好还是将它换成数字值进行比较。如:
console.log(document.getElementById("h1").nodeType == 1); //true
nodeName
和nodeValue
属性
在使用这两个属性之前,最好是先检测一下节点的类型:
if (someNode.nodeValue == 1){
value = someNode.nodeName;
}
console.log(document.getElementById("input").nodeName); //INPUT
console.log(document.getElementById("input").nodeValue); //null
对于元素节点(Node.ELEMENT_NODE),nodeName 中始终是元素的标签名,nodeValue 中则为 null。
节点关系
childNodes
属性和NodeList
对象和它的item()
方法
前者中保存着一个 NodeList 对象;
后者并不是 Array 的实例;
可以通过方括号和 item() 方法来访问在后者的节点;
如:
this is a paragraph
var para = document.getElementById("p");
var array = Array.prototype.slice.call(para.childNodes,0);
console.log(array);
上述代码在 IE8 中无效,因为 IE8 及更早版本将 NodeList 实现为一个 COM 对象。但下面的代码可以在所有浏览器中运行:
function convertToArray(nodes){
var array = null;
try{
array = Array.prototype.slice.call(nodes,0);
}catch(ex){
array = new Array();
for (var i=0; i
parentNode
属性和previousSibling
、nextSibling
属性
parentNode
属性指向文档树中的父节点;同胞节点中第一个节点的previousSibling
属性的值为null;同理最后一个节点的nextSibling
属性的值也为null。如:
if (someNode.nextSibling === null){
console.log("last node in the parent's childNodes list.");
} else if(someNode.previousSibling === null){
console.log("first node in the parent's childNodes list.");
}
firstChild
属性和lastChild
属性
这两个属性分别等于: someNode.childNodes[0]
和someNode.childNodes[someNode.childNodes.length - 1];
hasChildNodes()
方法和ownerDocument
属性
前者返回是否包含子节点的布尔值;后者指向表示整个文档的文档节点。
hello this is h1
var title = document.getElementById("h1");
console.log(title.childNodes) //["hello this is h1"]
hello this is h1
var title = document.getElementById("h1");
console.log(title.ownerDocument.nodeType) //9
操作节点
以下述代码为例:
this is a paragraph
appendChild()
方法
该方法用于向 childNodes 列表的末尾添加一个节点。其中,如果在 调用 appendChild() 时传入了父节点的第一个子节点,那么该节点就会成为父节点的最后一个子节点,如:
var txt = document.getElementById("p");
txt.appendChild(txt.firstChild);
console.log(txt.childNodes) //[, "this is a paragraph"]
insertBefore()
方法
用于把节点放在 childNodes 列表中某个特定的位置上。接收两个参数:要插入的节点和作为参照的节点,如:
var txt = document.getElementById("p");
var newNode = document.createElement("p");
var newNode_value = document.createTextNode("hello there");
newNode.appendChild(newNode_value);
txt.insertBefore(newNode, txt.lastChild);
document.write(Array.prototype.slice.call(txt.childNodes,0));//[object Text],[object HTMLParagraphElement],[object HTMLImageElement]
如果参照节点为 null,则 insertBefore() 与 appendChild() 执行相同的操作。
replaceChild()
方法
该方法接收两个参数:要插入的节点和要替换的节点,如:
var txt = document.getElementById("p");
var newNode = document.createElement("p");
var newNode_value = document.createTextNode("hello there");
newNode.appendChild(newNode_value);
txt.replaceChild(newNode, txt.lastChild);
document.write(Array.prototype.slice.call(txt.childNodes,0));//[object Text],[object HTMLParagraphElement]
removeChild()
方法
该方法接收一个参数:要删除的节点,如:
var txt = document.getElementById("p");
txt.removeChild(txt.lastChild);
document.write(Array.prototype.slice.call(txt.childNodes,0));//[object Text]
其他方法
cloneNode()
方法
该方法所有类型节点都具有;用于创建调用这个方法的节点的一个完全相同的副本。接收一个参数,表示是否执行深赋值。true 执行深复制,复制节点及整个子节点树;false 执行浅复制,只复制节点本身,后要通过 appendChild() 等方法添加到文档中。如:
- item 1
- item 2
- item 3
var myList = document.getElementById("unordered");
var deepList = myList.cloneNode(true);
console.log(deepList.childNodes.length); //7 (IE < 9 版本情况下为3,因为不会创建空白符节点。)
var shallowList = myList.cloneNode(false);
console.log(shallowList.childNodes.length); //0
normalize()
方法
该方法的作用是处理文档树中的文本节点。在出现文本节点不包含文本,或者连接出现两个文本节点的情况下,就删除它。
Document 类型
JavaScript 通过 Document 类型表示文档,在浏览器中,document 对象是 HTMLDocument 的一个实例,表示整个 HTML 页面。Document 节点具有以下特征:
console.log(document.nodeType + document.nodeName + document.nodeValue + document.parentNode + document.ownerDocument); //9#documentnullnullnull
以及其子节点可能是一个 DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction 或 Comment。
文档的子节点
documentElement
属性和body
属性
其子节点可能是一个 DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction 或 Comment。但还有两个内置的访问其子节点的方式:一个是 documentElement 属性,该属性始终指向 元素如:
var html = document.getElementsByTagName("html")[0];
console.log(document.documentElement === html); //true
var html = document.firstChild;
console.log(document.documentElement === html); //true
var html = document.childNodes[0];
console.log(document.documentElement === html); //true
以上代码建立在如下网页源代码之上:
另外一个则是 body 属性。直接指向 元素。
DocumentType
即doctype
属性
通常将 标签看成一个与文档其他部分不同的实体,可以通过 doctype 属性(document.doctype)查看。如:
浏览器对 document.doctype 的支持差别很大,主要有:
IE8 及之前的版本:当做注释,document.doctype 的值为 null;
IE9+ 及 Firefox:作为第一个子节点;
Safari、Chrome 和 Opera:不作为文档的子节点。
另外,按理说出现在 html 标签之外的注释也算是文档的子节点。如:
浏览器在处理位于 html 标签之外的注释方面存在如下差异:
IE8 及之前的版本、Safari 3.1 及更高的版本、Opera 和 Chrome 只为第一条注释创建节点;
IE9 及更高的版本将两个注释都创建为节点;
Firefox、Safari 3.1 之前的版本会忽略这两个注释。
文档信息
title
属性
document 对象的另一个属性是 title,包含这 title 元素中的文本,但是修改 title 属性的值并不会
改变 title 标签元素。如:
var x = setTimeout(setTitle, 1000);
function setTitle(){
document.title = "Hello";
var y = setTimeout(setTitle2, 1000);
function setTitle2(){
document.title = "World";
x = setTimeout(setTitle, 1000);
}
}
以上代码可以以1秒的速度自动切换显示 title 标签的内容
URL
、domain
和referrer
属性
三者分别包含完整的 URL;页面的域名以及连接到当前页面的那个页面的 URL。三个属性当中只有 domain 是可以设置的。如:
//假设页面来自 p2p.wrox.com 域
document.domain = "wrox.com"; //成功
document.domain = "baidu.com"; //失败
另外,由于跨域安全限制,来自不同的子域的页面无法通过 js 通信。还有,如果域名一开始是“松散的”(loose),那么就不能再设置为“紧绷的”(tight)。即将 document.domain 设置为"wrox.com"之后,就不能再将其设置回"p2p.wrox.com"否则将出错。
查找元素
getElementById()
方法
该方法接收一个参数即元素的 ID。以下面的元素为例:
Some text
可以使用下面的代码取得这个元素:
var div = document.getElementById("myDiv");
var div = document.getElementById("mydiv"); //无效的ID 但是可以在 IE7 及更早的版本中使用
如果文档中出现多个 ID 相同的元素,则取得第一次出现的那个。
IE7 及更早的版本在 name 特性与给定的 ID 匹配的表单元素(input textarea button select)也会被该方法访问。如:
Some text
console.log(document.getElementById("myElement"));
所以最好不要把 id 与 name 设置成同一个名称。
getElementsByTagName()
方法、HTMLCollection
属性以及namedItem()
方法
前者接收一个参数即要去的的元素的标签名,后者则是该方法返回的对象,如下代码将取得所有 img 元素,并返回一个 HTMLCollection 对象:
console.log(document.getElementsByTagName("img").length); //4
这里返回的 HTMLCollection 对象可以通过方括号语法或 item() 方法来访问:
另外,可以通过namedItem()
方法来获得指定的 name 特性的项如:
当然也可以使用方括号语法:
在后台,对数值索引就会调用 item(),而对字符串索引就会调用 namedItem()。
另外,可以向 getElementsByTagName() 中传入“*”。表示全部标签;
getElementsByName()
方法
该方法返回给定 name 特性的所有元素。最常用的情况是取得单选按钮。
在这里 namedItem() 方法只会取得第一项,因为每一项的 name 特性都是 color。
特殊集合(.anchors/.forms/.images/.links)
document.anchors:文档中所有带 name 特性的 a 元素;
document.applets:文档中的所有 applet 元素。已经不推荐使用 applet 元素了。document.forms:文档中所有 form 元素
document.images:文档中所有 img 元素
document.links:文档中所有带 href 特性的 a 元素;
如下:
abc
abc
abc
console.log(document.anchors.length); //2
console.log(document.forms.length); //2
console.log(document.links.length); //3
DOM 一致性检测
document.implementation
属性与hasFeature()
方法
由于 DOM 有多个级别,包含多个部分,因此检测浏览器实现了 DOM 的哪些部分是非必要。该方法接收两个参数:要检测的 DOM 功能的名称及版本号。如果浏览器支持,则返回 true,如:
console.log(document.implementation.hasFeature("XML", "1.0")); //True
列表如下:
Core 实现 Node、Element、Document、Text 和其他所有DOM实现都要求实现的基本接口 所有遵守 DOM 标准的实现都必须支持该模块。
HTML 实现 HTMLElement、HTMLDocument 和其他 HTML 专有接口。
XML 实现 Entity、EntityReference、ProcessingInstruction、Notation 和其他 XML 文档专用的节点类型。
StyleSheets 实现描述普通样式表的简单接口。
CSS 实现 CSS 样式表专有的接口。
CSS2 实现 CSS2Properties 接口。
Events 实现基本的事件处理接口。
UIEvents 实现处理用户界面事件的接口。
MouseEvents 实现处理鼠标事件的接口。
HTMLEvents 实现处理 HTML 事件的接口。
MutationEvents 实现处理文档变化事件的接口。
Range 实现操作文档范围的接口。
Traversal 实现进行高级文档遍历的接口。
Views 实现处理文档视图的接口。
文档写入
write()
、writeln()
、open()
以及close()
其中,writeln() 会在字符串最后添加一个换行符(\n)如:
document.write("hello");
document.writeln("there"); //最后面有个换行符
document.write("anybodyHome?");
//hellothere anybodyHome?
此外,还可以使用 write() 和 writeln() 方法动态地包含外部资源。
需要注意的是,如果在文档加载完毕后再执行 document.write 则会重写文档。
方法 open() 和 close() 分别用于打开和关闭网页的输出流。
Element 类型
该类型用于表现 XML 或 HTML 元素,提供了对元素标签名、子节点及特性的访问。主要特性如下:
var x = document.getElementsByTagName("p")[0];
console.log(x.tagName); //P
console.log(x.nodeName); //P
console.log(x.nodeType); //1
console.log(x.nodeValue); //null
console.log(x.parentNode); //HTMLDivElement
其子节点可能是 Element、Text、Comment、ProcessingInstruction、CDATASection 或 EntityReference。
因为 XML 和 HTML 的标签名大小写关系,在比较标签名的时候最好先转换成小写,如:
var x = document.getElementsByTagName("p")[0];
if (x.tagName === "p") {
console.log('testing1');
};
if (x.tagName.toLowerCase() === "p") {
console.log('testing2');
};
//testing2
HTML元素
所有 HTML 元素都由 HTMLElement 类型表示,标准特性如下:
id:元素的唯一标识符;
title:附加说明信息;
lang:语言代码;
dir:语言方向;
className:元素的 class 特性对应,即 CSS 类。
如:
var div = document.getElementById("bd");
console.log(bd.dir); //ltr
另外还有其他众多 HTML 元素以及与之关联的类型。这里不再累述。
取得特性
getAttribute()
方法
该方法主要是取得特性的值;
需要注意的是,任何元素的所有特性,都可以通过 DOM 元素本身的属性来访问。但是自定义的特性不会以属性的形式添加到 DOM 对象中。如:
由于各种原因,应该只有在取得自定义特性值的情况下,才会使用该方法,如:
另外需要注意的是,根据 HTML5 规范,自定义特性应该加上 data- 前缀以便验证,如:
设置特性
setAttribute()
方法
该方法接收两个参数:要设置的特性名和值。如果特性名存在,则执行替换操作。如:
综上所述,在取得特性值的情况下,最好用getAttribute()
方法取得自定义特性,用 DOM 属性访问公认的特性;在设置特性值的情况下,最好用 DOM 属性来设置公认的特性,用setAttribute()
方法设置自定义特性。如:
总之,最好直接通过属性来读取和设置特性。
removeAttribute()
方法
一个参数,IE6 及以前的版本不支持该方法。如:
attributes 属性
该属性包含一个 NamedNodeMap 对象,该对象有下列方法(不常用):
getNamedItem(Name):返回 nodeName 属性等于 Name 的节点;
removeNamedItem(Name):移除指定的节点;
setNamedItem(Node):添加指定的节点;
item(pos):返回位于数字 pos 位置的节点;
具体如下:
hhh
var pId = document.getElementById("pId");
var namedItem = pId.attributes.getNamedItem("id").nodeValue;
console.log(namedItem); //pId
var attrList = pId.attributes.item(0).nodeValue; //pId
console.log(attrList);
pId.attributes.removeNamedItem("id");
console.log(pId.id); //
该属性经常涌来遍历元素的特性,如:
hhh
var pElem = document.getElementById("pId");
function outputAttributes(element) {
var pairs = new Array();
var attrName, attrValue, i, len;
for (i = 0, len = element.attributes.length; i < len; i++) {
attrName = element.attributes[i].nodeName;
attrValue = element.attributes[i].nodeValue;
pairs.push(attrName + "=\"" + attrValue + "\"");
}
return pairs.join(" ");
}
console.log(outputAttributes(pElem)); //id="pId" name="para"
但是需要强调的是:
不通浏览器返回的顺序不通;
IE7 及更早的版本会返回 HTML 元素中所有可能的特性,包括没有指定的特性
每个特性节点都有一个名为 specified 的属性,这个属性的值如果为 true,则意味着要么是在 HTML 中制定了相应特性,要么是通过 setAttribute() 方法设置了该特性。
上面的代码兼容性考虑,应该改为下面这样:
hhh
var pElem = document.getElementById("pId");
function outputAttributes(element) {
var pairs = new Array();
var attrName, attrValue, i, len;
for (i = 0, len = element.attributes.length; i < len; i++) {
attrName = element.attributes[i].nodeName;
attrValue = element.attributes[i].nodeValue;
if (element.attributes[i].specified) {
pairs.push(attrName + "=\"" + attrValue + "\"");
};
}
return pairs.join(" ");
}
console.log(outputAttributes(pElem)); //id="pId" name="para"
创建元素
createElement()
方法
该方法接收一个参数,即要创建元素的标签名。用该方法创建的新元素被赋予了ownerDocument
属性。随后通过appendChild()
等方法添加到文档树中。如:
var newElementP = document.createElement("p");
var newElementPValue = document.createTextNode("data");
newElementP.title = "im title";
newElementP.appendChild(newElementPValue);
document.body.appendChild(newElementP);
console.log(newElementP.lastChild.nodeValue); //data
console.log(newElementP.title); //im title
在 IE 中可以用另一种方式使用 createElement(),即为这个方法传入完整的元素标签,也可以包含属性,但这种方法存在很多问题,不建议使用。(具体见《js高级程序设计》第十章节点层次 Element 类型)
元素的子节点
以下面代码为例:
- 1
- 2
- 3
var list = document.getElementById("ulList");
var childNodesList = list.childNodes;
console.log(childNodesList.length); //7
for (var i = childNodesList.length - 1; i >= 0; i--) {
if (childNodesList[i].nodeType == 1) {
console.log(childNodesList[i].nodeName); //LI*3
};
};
for (var i = childNodesList.length - 1; i >= 0; i--) {
if (childNodesList[i].nodeType == 3) {
document.write(childNodesList[i].nodeValue); //LI*3的text节点值1、2、3
};
};
如同上面的代码一样,如果需要通过 childNodes 属性遍历子节点,呢么一定要在操作以前检查 nodeType 属性。因为不同的浏览器会返回不同的节点个数:
- 1
- 2
- 3
这里才会返回 childNodesList.length 为3。
如果要返回上面代码中的所有 li 元素,可以使用如下代码:
var ulList = document.getElementById("ulList");
var liList = ulList.getElementsByTagName("li");
console.log(liList.length); //3
Text 类型
要注意的是,字符串会经过 HTML 或 XML 编码。如:
var pElem = document.getElementById("pId");
pElem.firstChild.nodeValue = "hello there
";
console.log(pElem.innerHTML); //<p>hello there</p>
创建文本节点
createTextNode()
方法
该方法创建文本节点,接收一个参数即文本字符串。按理说一个元素只有一个文本节点,但是如果为其赋予了多个节点,那么这些节点中的文本就会连起来,并且中间没有空格。如:
var newP = document.createElement("p");
var newPValue = document.createTextNode("data");
newP.appendChild(newPValue);
document.body.appendChild(newP);
var anotherPValue = document.createTextNode("anotherData");
newP.appendChild(anotherPValue); //dataanotherData
规范化文本节点
normalize()
方法
该方法在一个包含两个或多个文本节点的父元素上调用,将会把所有文本节点合成成一个节点。如:
var newP = document.createElement("p");
var newPValue = document.createTextNode("data");
newP.appendChild(newPValue);
document.body.appendChild(newP);
var anotherPValue = document.createTextNode("anotherData");
newP.appendChild(anotherPValue); //dataanotherData
console.log(newP.childNodes.length); //2
newP.normalize();
console.log(newP.childNodes.length); //1
分割文本节点
splitText()
方法
这个方法将一个文本节点分成两个文本节点,原来的文本节点包含从开始到指定位置之前的内容;新的文本节点将包含剩下的文本。这个方法会返回一个新文本节点。如:
var newP = document.createElement("p");
var newPValue = document.createTextNode("data");
newP.appendChild(newPValue);
document.body.appendChild(newP);
var anotherPValue = document.createTextNode("anotherData");
newP.appendChild(anotherPValue); //dataanotherData
newP.normalize();
var anotherNewP = newP.firstChild.splitText(4);
console.log(newP.firstChild.nodeValue); //data
console.log(newP.lastChild.nodeValue); //anotherData
newP.normalize();
console.log(newP.firstChild.nodeValue); //dataanotherData
Comment 类型
createComment()
方法
该方法可以创建注释节点。不过鲜有人使用。拥有除 splitText() 之外的所有字符串操作方法。
CDATASection 类型
该类型只有在真正的 XML 文档中可以使用以下方法,浏览器会解释他为 Comment 类型:
console.log(document.getElementById("myDiv").firstChild); //Comment
createCDataSection()
方法
该方法用来创建 CDATA 区域,只需为其传入节点的内容即可
DocumentType 类型
不常用。包含着与文档的 doctype 有关的所有信息。
该类型对象的三个属性:name、entities 和 notations。其中,name 表示文档类型的名称;entities 是由文档类型描述的实体的 NamedNodeMap 对象;notations 是由文档类型描述的符号的 NamedNodeMap 对象。一般来说只有 name 有点用。
console.log(document.doctype.name); //html
IE 不支持。
DocumentFragment 类型
createDocumentFragment()
方法
主要作用就是用来避免通过逐个添加元素所导致的浏览器反复渲染新信息的问题,使用一个文档片段来保存创建的元素,然后再一次性将它们添加到文档中。如:
var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
var li = null;
for (var i = 0; i < 3; i++) {
li = document.createElement("li");
li.appendChild(document.createTextNode("Item" + (i + 1)));
fragment.appendChild(li);
};
ul.appendChild(fragment);
Attr 类型
该对象又3个属性:name、value 和 specified。但使用getAttribute()
、setAttribute()
和removeAttribute()
方法更好用。