4.客户端JavaScript笔记:DOM

本系列内容由ZouStrong整理收录

整理自《JavaScript权威指南(第六版)》,《JavaScript高级程序设计(第三版)》

DOM(文档对象模型)是一个与系统平台和编程语言无关(跨平台,语言中立)的接口(API),程序和脚本可以
通过这个接口动态的访问和修改文档内容、结构、样式

DOM将文档抽象为一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分

每一个window对象都有一个document属性,它指代document对象,代表了脚本所在的文档——这就是DOM的核心对象

注:IE中的所有DOM对象都是以COM对象的形式实现的。这意味着IE中的DOM 对象与原生JavaScript对象的行为或活动特点并不一致

一. 节点层次

DOM将整个文档映射为一个多层节点树

文档节点是每个文档的根节点(document),它有两个子节点——文档类型声明(document.doctype)和html元素(document.documentElement)

此外还有元素节点、文本节点、注释节点等

  • 每一个节点都是一个Node对象,它代表所有类型的节点
  • 其中根节点是Document对象,它代表整个文档
  • 其中元素节点是Element对象,它代表HTML元素
  • 其中文本节点是Text对象,它代表文本节点
  • Document对象、Element对象、Text对象都是Node对象的子类

通用的Document和ELement类型与HTMLDocument和HTMLELement类型之间没有严格的区别,只不过前者针对HTML或者XML文档,后者只针对HTML文档,通常都是使用通用的Document和ELement类型

总共有12种节点类型,这些类型都继承自一个基类型——Node类型

二. Node类型

JavaScript中的所有节点类型都继承自Node类型,因此所有节点类型都共享着相同的基本属性和方法

nodeType属性

每个节点都有一个nodeType属性,用于表明节点的类型(返回一个常量表示的整数值,但是使用常量值会在低版本IE下报错,所以要使用整数值)

  • 1——元素节点
  • 2——属性节点
  • 3——文本节点
  • 4——注释节点

  • 9——document节点
  • ...

通过比较数值,很容易确定节点类型

if (someNode.nodeType == 1){
	alert("这是一个元素节点");
}

nodeName属性

该属性返回节点名称(始终都是大写),通常只对元素节点有用,返回的就是这个元素的标签名(其他节点则返回null),因此使用这个值以前,最好先检测一下节点的类型

if (someNode.nodeType == 1){
	alert(someNode.nodeName);
}

nodeValue属性

该属性返回节点值,通常只对文本节点或注释节点有用,使用这个值以前,最好先检测一下节点的类型

if (someNode.nodeType == 3){
	alert(someNode.nodeValue);
}

对于文本和注释节点,nodeValue属性值等于它们的data属性值

parentNode属性

返回父节点(对于document节点,则返回null)

childNodes属性

该属性返回一个NodeList对象(类数组对象),该对象保存着元素的所有子节点的实时表示

可以通过length属性返回子节点的个数(hasChildNodes()方法在length属性大于0时返回true)

通过方括号语法(常用)或者item()方法来访问这些节点

注:这个属性返回所有类型的子节点,包括了元素节点、文本节点甚至空格,回车

遍历子节点,那么一定不要忘记浏览器间的这一差别。这意味着在执行某项操作以前,通常都要先检查一下nodeTpye属性

for (var i=0, len=ele.childNodes.length; i < len; i++){ 
	if (ele.childNodes[i].nodeType == 1){ 
		//执行某些操作
	} 
} 

注:NodeList对象的独特之处在于,它实际上是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反映在NodeList对象中。我们常说,NodeList是有生命、有呼吸的对象,而不是在我们第一次访问它们的某个瞬间拍摄下来的一张快照,而这也正是操作DOM比较耗内存的原因

firstChild、lastChild属性

返回childNodes列表中的第一个(最后一个)节点,在只有一个子节点的情况下,firstChild 和 lastChild指向同一个节点;如果没有子节点,那么firstChild和lastChild的值均为null

someNode.firstChild==someNode.childNodes[0]
someNode.lastChild==someNode.childNodes[someNode.childNodes.length-1]

nextSibling、previousSibling属性

返回节点的下一个(上一个)兄弟节点,最后一个节点的nextSibling属性的值为null,第一个节点的previousSibling属性值为null

注:nextSibling属性和previousSibling属性返回的节点中,都把空格计算在内

ownerDocument属性

所有节点都有的最后一个属性——ownerDocument属性,它返回整个文档的文档节点(document)

除了这些属性之外,Node类型还有以下方法

hasChildNodes()方法

检测是否包含子节点

注:也可查询childNodes列表的length属性来达到同样效果

appendChild()方法

用于向节点末尾添加一个节点,返回新增的节点

var returnedNode = someNode.appendChild(newNode); 

注:如果传入到appendChild()中的节点已经是文档的一部分了,那结果就是将该节点从原来的位置转移到新位置

insertBefore()方法

接受两个参数:要插入的节点和作为参照的节点,返回插入的节点。如果参照节点是null,则insertBefore()与appendChild()执行相同的操作

注:insertBefore()方法用在父节点上

replaceChild()方法

接受两个参数:要插入的节点和要替换的节点,返回被替换的节点

removeChild()方法

这个方法接受一个参数,即要移除的节点,返回被移除的节点

注:被移除的节点仍然为文档所有,只不过在文档中已经没有了自己的位置,后续可以继续插入等操作

注:前面四个方法操作的都是某个节点的子节点,也就是说,要使用这几个方法必须先取得父节点(使用parentNode属性)

cloneNode()方法

该方法用于复制节点,返回复制的节点

该方法接受一个布尔值参数表示是否执行深度复制,在参数为true的情况下,执行深复制,也就是复制节点及其整个子节点树;在参数为false的情况下,执行浅复制,即只复制节点本身

返回的节点副本是一个“孤儿”,除非通过appendChild()、insertBefore()等将它添加到文档中

cloneNode()方法不会复制添加到DOM节点中的JavaScript属性,例如事件处理程序等

注:IE存在一个bug,即它会复制事件处理程序,所以在复制之前最好先移除事件处理程序

normalize()方法

处理文档树中的文本节点

三. Document类型

Document类型表示整个文档(XML文档,HTML文档)

在浏览器中,document对象是HTMLDocument类型(继承
自Document类型)的一个实例,表示整个 HTML 页面。而且,document对象是window对象的一个属性,因此可以将其作为全局对象来访问

Document节点具有下列特征

  • nodeType的值为9
  • nodeName的值为"#document"
  • nodeValue的值为null
  • parentNode的值为null
  • ownerDocument的值为null
  • 可能有两个子节点——文档类型声明(document.doctype)和html元素(document.documentElement)

最常见的应用还是HTMLDocument的实例——document对象(通过它,不仅可以取得与页面有关的信息,而且还能操作页面的外观及其底层结构)

1. 文档的后代节点

DOM标准规定Document节点的子节点可以是DocumentType、Element、ProcessingInstruction或Comment

有两个内置的访问其子节点的快捷方式

  • document.documentElement属性指向html元素
  • 可以通过document.childNodes列表访问html元素

var body = document.documentElement;

document对象还有一个body属性,指向body元素

var body = document.body;

所有浏览器都支持这两个属性

Document另一个可能的子节点是DocumentType(文档类型声明),可以通过 document.doctype属性来获取

var doctype = document.doctype;

但是document.doctype的支持差别很大(IE8及其之前浏览器返回null)

最后,ECMAScript5新增了head属性,返回head元素

var head = document.head;  //IE8及其以下不支持

2. 文档信息

document对象还有一些属性提供了网页的一些信息

其中第一个属性就是title属性,返回title元素中的文本,而且它是可读可写的,因此可以通过它来设置网页的title

var originalTitle = document.title;
document.title = "New page title";

还有3 个属性都与网页请求有关,它们是URL、domain和referrer

URL属性中包含页面完整的url,相当于location.href,但却是只读的

var url  =document.URL;

domain属性中只包含页面的域名,通过它可以返回或设置页面的域名(但不会显示在地址栏中)

var domain  =document.domain;

referrer属性返回跳转到当前页面的那个页面的URL,或者在没有来源页面的情况下,referrer属性包含空字符串

var referrer  =document.referrer;

在这3个属性中,只有domain是可以设置的。但出于安全考虑,不可以给 domain随意设置值——不能将这个属性设置为URL中不包含的域

//假设页面来自zou.strong.com域
document.domain = "strong.com";      // 成功!
document.domain = "strongger.com"; // 出错!

当页面中包含来自其他子域的框架或内嵌框架时,能够设document.domain 就显的很有用

由于跨域安全限制,来自不同子域的页面无法通过JavaScript 通信。而通过将每个页面的document.domain设置为相同的值,这些页面就可以互相访问对方包含的JavaScript对象了

假设有一个页面加载自www.strong.com,包含一个内嵌框架,框架内的页面加载自zou.strong.com,由于document.domain不同,两个页面之间无法相互访问对方的JavaScript对象。但如果将这两个页面的document.domain值都设置为"strong.com",它们之间就可以通信了

此外,浏览器对domain属性还有一个限制,即如果域名一开始是“松散的”,那么不能将它再设置为“紧绷的”

//假设页面来自zou.strong.com域
document.domain = "strong.com";       // 成功!
document.domain = "zsz.strong.com"; // 出错!

document对象还有一些特殊的属性,以下这些属性返回一个HTMLCollection对象

  • document.anchors属性——返回所有带name特性的a元素
  • document.links——返回所有带href特性的a元素
  • document.forms——返回中所有的form元素
  • document.images——返回所有的img元素

3. 获取元素的方法

除了上面的属性可以获取元素之外,更通用的方式就是使用document对象的几个方法来完成

Document类型为此提供了两个方法

getElementById()方法

接收一个参数,元素ID,返回带有相应ID的元素(或者没有的话,则返回 null)

参数ID必须与页面中元素的id特性(attribute)严格匹配,包括大小写

如果页面中多个元素的ID值相同(当然,这一般是不允许的), getElementById()只返回文档中第一次出现的元素

IE7及之前版本有BUG,name特性与给定ID参数匹配的表单元素也会被该方法返回。如果有哪个表单元素的 name 特性等于指定的 ID,而且该元素在文档中位于带有给定 ID 的元素前面,那么 IE 就会返回那个表单元素

<input type="text" name="myElement">
<div id="myElement"></div>

在IE7中调用 document.getElementById("myElement "),结果会返回input元素;而在其他所有浏览器中,都会返回对

元素的引用。为了避免 IE 中存在的这个问题,最好的办法是 不让表单字段的name特性与其他元素的ID相同

getElementsByTagName()方法

接受一个参数,元素名(不区分大小写),返回包含零或多个元素的HTMLCollection 对象,作为一个“动态”集合,该对象与 NodeList 非常类似

var images = document.getElementsByTagName("img");

可以使用方括号语法或item()方法来访问HTMLCollection对象中的项,通过length属性获得元素的个数

此外,HTMLCollection对象还有一个namedItem()方法,使用这个方法可以通过元素的name特性取得集合中的项

<img src="myimage.gif" name="myImage">
var images = document.getElementsByTagName("img");
var myImage = images.namedItem("myImage");

第三个方法

getElementsByName()方法

接受一个参数,元素的name特性,返回包含零或多个元素的HTMLCollection 对象

4. 文档写入

document对象具有向文档中写入内容的能力,有以下四个方法write()、writeln()、open()和close()(他们在DOM之前就出现了,现在已经很少使用了)

write()和writeln()会将参数字符串拼接起来,然后插入到文档中调用他的脚本的位置(后者会多插入一个换行符\n),当脚本执行完毕,浏览器解析生成的输出并显示它

只有解析文档时,才能使用document.write()输出内容,这是因为这些脚本的执行也是文档解析流的一部分,如果document.write()被放在一个函数中,而函数是作为某个事件处理程序调用的,他可能就会重写整个文档

window.onload = function(){
	document.write("Hello world!");  //重写整个文档
};

在设置了defer或async属性的脚本中,不允许使用document.write()

方法 open()和 close()分别用于打开和关闭网页的输出流。如果是在页面加载期间使用 write()
或 writeln()方法,则不需要用到这两个方法

四. Element类型

Element类型表示XML或HTML元素,可以方便的获取元素名,子节点和特性

Element节点具有下列特征

  • nodeType的值为1
  • nodeName的值为标签名
  • nodeValue的值为null
  • parentNode的值可能是Document或Element

使用nodeName或者tagName属性,可以获取大写的标签名(后者的意图更加清晰)

注意的是,在HTML中,标签名始终都以全部大写表示;而在 XML(有时候也包括 XHTML)中,标签名则始终会与源代码中的保持一致

因此,最好先转换一下再比较

if (element.tagName == "div"){ 
	//不能这样比较,很容易出错!
}
if (element.tagName.toLowerCase() == "div"){
	//这样最好(适用于任何文档)
}

1. HTML元素

所有HTML元素都由HTMLElement类型或者其子类型(HTMLDivElement、HTMLParagraphElement.....)表示

HTMLElement类型拥有一些特有的属性,因此每个html元素都继承了下列标准属性(他们都是可读、可写的)

  • id属性——返回元素的id值
  • title属性——返回元素的title值
  • className属性——返回元素的class值
  • lang属性——返回元素的语言代码
  • dir属性——返回元素的语言方向

    div.id; //获取值
    div.id = "strong"; //设置值

所有标准特性(非自定义的)都可以通过元素对象属性的形式访问到,访问不存在的特性会返回undefined

2. HTML元素特性

元素对象属性可以方便的获取或设置元素的标准特性,但是对于自定义特性却无能为力

因此需要使用更加通用的方法,可以针对任何特性使用

getAttribute()方法

该方法接受一个特性名,返回对应的特性值,不存在的话,返回null

var div = document.getElementById("myDiv");
alert(div.getAttribute("id"));
alert(div.getAttribute("class"));
alert(div.getAttribute("my_special_attribute"));  //取得自定义特性 
//根据 HTML5 规范,自定义特性应该加上data-前缀以便验证

注意,传递给 getAttribute()的特性名与实际的特性名相同。因此要想得到 class 特性值,应该传入"class"而不是"className",后者只有在通过对象属性访问特性时才用

有两类特殊的特性,它们虽然有对应的属性名,但属性的值与通过 getAttribute()返回的值并不相同

  • 第一类特性就是style,在通过getAttribute()访问时,返回的style特性值中包含的是 CSS 文本,而通过属性来访问它则会返回一个对象
  • 第二类与众不同的特性是onclick这样的事件处理程序,在通过getAttribute()访问,则会返回相应代码的字符串。而在访问onclick 属性时,则会返回一个 JavaScript 函数

由于存在这些差别,在通过JavaScript以编程方式操作DOM时,不经常使用getAttribute(),而是只使用对象的属性;只有在取得自定义特性值的情况下,才会使用getAttribute()

setAttribute()方法

该方法接受两个参数:要设置的特性名和值(如果特性已经存在,则会以指定的值替换现有的值)

div.setAttribute("id", "strong");
div.setAttribute("CLASS", "strong");

通过这个方法设置的特性名会被统一转换为小写形式,即"CLASS"最终会变成"class"

因为所有特性都是属性,所以直接给属性赋值可以设置特性的值

div.id = "someOtherId";

但是为元素添加一个自定义的属性,则不会自动成为元素的特性

div.mycolor = "red";
alert(div.getAttribute("mycolor"));  //null( IE 除外)

在 IE7 及以前版本中, setAttribute()设置class 和 style 特性,无效

removeAttribute()方法

该方法接受一个特性名,用于彻底删除该特性

div.removeAttribute("class");

IE6及以前版本不支持

3. attributes属性

attributes属性是元素节点特有的属性,它返回一个该元素所有特性组成的NamedNodeMap对象(与 NodeList 类似,也是一个“动态”的集合)

这个对象拥有以下属性和方法

  • length属性:返回该元素包含的特性的个数
  • getNamedItem(name):返回 nodeName 属性等于 name 的节点
  • removeNamedItem(name):从列表中移除 nodeName 属性等于 name 的节点
  • setNamedItem(node):向列表中添加节点,以节点的 nodeName 属性为索引
  • item(pos):返回位于数字 pos 位置处的节点(也可以使用方括号语法)

返回的节点的nodeName就是特性的名称, 而节点的nodeValue就是特性的值

//取得元素的 id 特性
var id = element.attributes.getNamedItem("id").nodeValue;
//简写方式
var id = element.attributes["id"].nodeValue;

removeNamedItem()方法与在元素上调用 removeAttribute()方法的效果相同——直接删
除具有给定名称的特性

setNamedItem()方法可以为元素添加一个新特性

element.attributes.setNamedItem(newAttr);

attributes属性的方法不够方便(遍历元素的特性倒是可以),更常用getAttribute()、 removeAttribute()和 setAttribute()方法

4. 创建元素

document.createElement()方法可以创建新元素

接受一个参数,要创建的元素名

var div = document.createElement("div");

此时,该元素虽然还没有被插入到文档中,但是却拥有了ownerDocument 属性

此后,就可以操作元素的特性,为它添加更多子节点,以及执行其他操作

div.id = "myNewDiv";
div.className = "box";

要把新元素添加到文档树,可以使用 appendChild()、 insertBefore()或 replaceChild()方法

document.body.appendChild(div);

一旦将元素添加到文档树中,浏览器就会立即呈现该元素。此后,对这个元素所作的任何修改都会实时反映在浏览器中

5. 元素的子节点

元 素 的childNodes属性中包含了它的所有子节点,这些子节点有可能是元素、文本节点(包括空白符)、注等(不同的浏览器处理起来是不同的,因此length属性可能不一样)

这意味着在执行某项操作以前,要先检查一下nodeTpye属性

var len = element.childNodes.length;
for (var i=0;i < len;++){
	if (element.childNodes[i].nodeType == 1){
	//执行某些操作
	}
}

此外元素节点上也支持getElementsByTagName()方法,结果只会返回当前元素的后代元素

五. Text类型

Text类型表示文本节点,包含的是纯文本内容

  • nodeType 的值为 3
  • nodeName 的值为"#text"
  • nodeValue 的值为节点所包含的文本
  • parentNode 是一个 Element
  • 没有子节点

可以通过nodeValue属性或data属性访问和设置Text节点中包含的文本(两者作用完全相同)

<div id="s">文本内容</div>
alert(window.s.firstChild.data);   //"文本内容"
alert(window.s.firstChild.nodeValue);   //"文本内容"
window.s.firstChild.data = "新文本内容"; //改变元素内的文本

这里需要理解的就是,window.s.firstChild返回的只是Text对象,要想取得字符串文本值,还要使用nodeValue属性或data属性

使用下列方法可以操作节点对象中的文本

  • appendData(text):将 text 添加到节点的末尾
  • deleteData(index,n):从index位置开始删除n个字符
  • insertData(index,text):在index位置插入text
  • replaceData(index,n,text):将从index开始的n个字符替换为text
  • splitText(index):从index指定的位置将当前文本节点分成两个文本节点
  • substringData(index, n):提取从index开始的n个字符

此外,节点对象还有一个length属性,保存着节点中字符的数目(与它的nodeValue.length和data.length相同)

1. 创建文本节点

使用document.createTextNode()创建文本节点,这个方法接受一个参数——要插入节点中的文本

var tNode = document.createTextNode("123");

在创建新文本节点的同时,也会为其设置ownerDocument 属性

默认情况下,元素最多只能有一个文本节点

<!-- 没有内容,也就没有文本节点 -->
<div></div>
<!-- 有空格,因而有一个文本节点 -->
<div> </div>
<!-- 有内容,因而有一个文本节点 -->
<div>Hello World!</div>

不过,在某些情况下也可能包含多个文本子节点(浏览器在解析文档时永远不会创建相邻的文本节点。这种情况只会在执行DOM操作时出现)

var tNode1 = document.createTextNode("12");
element.appendChild(tNode1);
var tNode2 = document.createTextNode("34");
element.appendChild(tNode2);

2. 规范化文本节点

上面产生的就是两个文本节点

alert(element.childNodes[0].data);  //"12"
alert(element.childNodes[1].data);  //"34"

但是,对于经常操作DOM的我们来说,我们并不希望区分这两个文本节点,于是一个能够将相邻文本节点合并的方法应运而生

normalize()方法,是由Node类型定义的(因而在所有节点类型中都存在)如果在一个包含两个或多个文本节点的父元素上调用 normalize()方法,则会将所有文本节点合并成一个节点

element.normalize();
alert(element.childNodes[0].data);  //"1234"

normalize()方法会导致IE6的某些版本崩溃

3. 分割文本节点

splitText()方法与normalize()作用相反,它会将一个文本节点分成两个文本节点

接受一个数字参数,表示分隔的位置(该方法实在文本对象上调用的,不是在父元素上)

element.normalize();
alert(element.childNodes[0].data);  //"1234"
element.firstChild.splitText(2);
alert(element.childNodes[0].data);  //"12"
alert(element.childNodes[1].data);  //"34"

六. Attr类型

尽管它们也是节点,但特性却不被认为是 DOM 文档树的一部分。开发人员最常使用的是 getAttribute()、 setAttribute()和 remveAttribute()方法,很少直接引用特性节点

七. Comment类型

......

八. CDATASection类型

......

九. DocumentType类型

......

十. DocumentFragment类型

在所有节点类型中,只有DocumentFragment在文档中没有对应的标记

DOM 规定文档片段(document fragment)是一种“轻量级”的文档,可以包含和控制节点,但不会像完整的文档那样占用额外的资源

虽然不能把文档片段直接添加到文档中,但可以将它作为一个“仓库”来使用,即可以在里面保存将来可能会添加到文档中的节点

首先,创建文档片段

var fragment = document.createDocumentFragment();

文档片段继承了Node的所有方法

如果将文档中的节点添加到文档片段中,就会从文档树中移除该节点;可以通过 appendChild()或 insertBefore()将文档片段中内容添加到文档中。在将文档片段作为参数传递给这两个方法时,实际上只会将文档片段的所有子节点添加到相应位置上;文档片段本身永远不会成为文档树的一部分

<ul id="myList"></ul>

假设我们想为这个

    元素添加3个列表项。如果逐个地添加列表项,将会导致浏览器反复渲染(呈现)新信息。为避免这个问题,可以像下面这样使用一个文档片段来保存创建的列表项,然后再一次性将它们添加到文档中

    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);

你可能感兴趣的:(JavaScript)