参考书:《JavaScript高级程序设计》
知识点前提:什么是节点
Node类型
DOM1级定义了一个Node接口,该接口将DOM中所有节点类型实现。Node接口在JavaScript中是作为Node类型实现的;除了IE之外,在其他所有浏览器都可以访问到这个类型。
nodeType
只读
每个节点都有一个nodeType属性,有用于表明节点的类型。节点类型由在Node类型中定义的下列12个数值常量表示,任何节点类型必占据一种。
- Node.ELEMENT_NODE(1);
- Node.ATTRIBUTE_NODE(2);
- Node.TEXT_NODE(3);
- Node.CDATA_SECTION_NODE(4);
- Node.ENTITY_REFERENCE_NODE(5);
- Node.ENTITY_NODE(6);
- Node.PROCESSING_INSTRUCTION_NODE(7);
- Node.COMMENT_NODE(8);
- Node.DOCUMENT_NODE(9);
- Node.DOCUMENT_TYPE_NODE(10);
- Node.DOCUMENT_FRAGMENT_NODE(11);
- Node.NOTATION_NODE(12);
test1
Fuck!
i'm h1
使用Node类型常量来比较。
由于IE没有公开Node类型函数,因此上面代码在IE中会导致错误。为确保浏览器兼容,最好将nodeType属性与数字进行比较
test2
Fuck!
i'm h1
不是所有节点都会受到浏览器支持,最常用的就是元素和文本节点。
MDN —— NodeType
nodaName | nodeValue
nodaName
只读
nodaValue
可写可读
使用nodeName和nodeValue可以具体了解节点信息,这两个属性的值完全取决于节点的类型。
可以检测节点类型后在使用其属性,下图列出了不同节点类型的nodeValue的返回值。
所以,对于element这样的节点,返回值都为null。
test3
Fuck!
i'm h1
可以这样
test4
Fuck!
i'm h1
使用getAttributeNode方法,输入属性名,返回其attribute节点对象,然后使用NodeValue方法,返回值就为实际属性值。
节点关系
文档中所有的节点之间都存在这样或者那样的关系。节点间的各种关系可以用传统的家族关系来描述,每个节点存在childNodes属性,其中保存着一个Nodelist对象,是一种类数组对象,保存一组有序的节点,可以通过位置来访问这些节点。
虽然可以使用方括号语法来访问Nodelist的值,而且这个对象也有length属性,但它并不是Array的实例。Nodelist对象的独特之处在于,它实际上是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反映在Nodelist对象中。
我们常说,Nodelist是有生命、有呼吸的对象,而不是在我们第一次访问它们的某个瞬间拍摄下来的一张快照。
test5*
Fuck!
i'm P
i'm H1
可以看到其中childNodes属性里,Nodelist对象的传承,包含了从根目录下的所有节点。 i'm P
其中,#div的Nodelist里,是否发现了有5个节点,但实际上源代码只有2个元素啊,其实[0],[2],[4]
所代表的#text是我们源代码换行时留下的空位,在Nodelist里就用#text来表示了。
i'm H1
修改后,将只有两个element。
注意,length属性表示的是访问Nodelist的那一刻,其中包含的节点数量。
对于JS函数内部类数组对象arguments对象使用Array.prototype.slice()
方法可以将其转换为数组。而采用同样的方法,也可以将Nodelist对象转换为数组。
test6
Fuck!
i'm P
i'm H1
为方便演示,缩小范围,只抽取了#div做演示,
白箭头即为转换后的真数组,对转换后数组进行了一次数组splice增添节点操作,那么,是否会映射到Nodelis上呢?
红箭头是Nodelist对象,看看它,依然不变。
当然,通过Nodelist对象方法添加是可以,后续~
对于刚才的Nodelist对象转换数组方法,由于在IE8及更早版本将Nodelist实现为一个COM对象,而我们不能像使用JavaScript对象那样使用这种对象,因此原方法会导致错误,要想在IE中Nodelist转换为数组,必须手动枚举所有成员。
下列代码在所有浏览器均可运行:
test7
Fuck!
i'm P
i'm H1
Array(5) [#text, p, #text, h1, #text]
函数首先尝试了创建数组的最简单的方式。如果导致错误(说明在IE8及更早版本中),则通过try-catch来捕获错误,然后手动创建数组。
每个节点上都有一个parentNode属性,该属性指向文档树中的父节点。包含在childNodes列表中的所有节点都具有相同的父节点,因此它们的parentNode属性都指向同一个节点。此外,包含在childNodes列表中的每个节点相互之间都是同胞节点(siblingNode)。通过使用列表中的每个节点的previousSibling和nextSibling属性,可以访问同一列表中的其他节点。
关于节点关系,主要就是子节点、节点、父节点之间的定位关系。
test8
Fuck!
i'm P
i'm H1
在反映这些关系的所有属性当中,childNodes属性与其他属性相比更方便一些,因为只须使用简单的关系指针,就可以通过它访问文档树中的任何节点。另外,hasChildNodes()也是一个非常有用的方法,这个方法在节点包含一或多个子节点的情况下返回true;应该说,这是比查询childNodes列表的length属性更简单的方法。
所有节点都有的最后一个属性时ownerDocument,该属性指向表示整个文档的文档节点。这种关系表示的是任何节点都属于它所在的文档,任何节点都不能同时存在与两个或多个文档中。通过这个属性,我们可以不必在节点层次中通过层层回溯到达顶端,而是可以直接访问文档节点。
操作节点
因为关系指针都是只读的,所以DOM提供了一些操作节点的方法。其中,最常用的方法时appendChild(),用于向childNodes列表的末尾添加一个节点。添加节点后,childNodes的新增节点、父节点及以前的最后一个子节点的关系指针都会相应的得到更新(因为childNodes属性里的Nodelist对象更新了)。更新完成后,appendChild()返回新增的节点。
test9
Fuck!
i'm P
i'm H1
如果传入到appendChild()中的节点已经是文档中的一部分了,那结果就是将该节点从原来的位置转移到新位置。即使可以将DOM数看成是由一系列指针连接起来的 ,但任何DOM节点也不能同时出现在文档中的多个位置上。因此,如果在调用appenChild()时传入了父节点的第一个子节点,那么该节点就会成为父节点的最后一个子节点。
test10
Fuck!
i'm P
如果需要把节点放在childNodes列表中某个特定的位置上,而不是放在末尾,那么可以使用inserBefore()方法。这个方法接受两个参数:要插入的节点和作为参照的节点。插入节点后,被插入的节点会变成参照节点的前一个同胞节点(preivousSibling),同时被方法返回,如果参照节点是null,则insertBefore()与appendChild()执行相同的操作。
test11
Fuck!
i'm P
i'm H1
注意,如果将代码换行,保证可读性的同时,会使Nodelist对象增加若干的#text
节点,这是换行导致的。
如若要替换节点可使用replaceChild()方法
方法接受两个参数:要插入的节点,要替换的节点。要替换的节点将由这个方法返回并从文档树中移除,同时插入的节点占据其位置。
test12
Fuck!
i'm P
i'm H1
使用replaceChild()插入一个节点时,该节点所有关系指针都会从被它替换的节点赋值过来,尽管从技术上讲,被替换的节点还在文档中,但它在文档中已经没有了自己的位置。
如果只想移除而非替换节点,可以使用removeChild()方法。
方法接受一个参数,即要移除的节点。被移除的节点将成为方法的返回值。
test13
Fuck!
i'm P
i'm H1
有两个方法是所有类型节点都有的。
-
cloneNode()
-
normalize()
cloneNode()方法接受一个布尔值参数,表示是否执行深复制。参数为true情况下,执行深复制,也就是复制节及其整个子节点树;在参数为false情况下,执行潜复制,即只复制节点本身。复制后返回的节点副本属于文档所有,但并没有为它指定父节点。因此这个节点副本就成为了一个“孤儿”,除非通过appedChild,insertBefore,replaceChild将它添加到文档中。
test14
Fuck!
- item 1
- item 2
- item 3
cloneNode()不会赋值添加到DOM节点中的JavaScript属性,例如事件处理程序等,这个方法只复制特性、子节点(true),其他一切都不会复制。
normalize()
这个方法唯一作用就是处理文档树中的文本节点。
由于解析器的实现或者DOM操作等原因,可能会出现文本节点或者不包含文本,或者接连出现两个文本节点的情况。
当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到空白文本节点,则删除它,如果找到相邻的文本节点,则将它们合并为一个文本节点。
Document类型
JavaScript通过Document类型表示文档。在浏览器汇总,document对象是HTMLDocument(继承自Document类型)的一个实例,表示整个HTML页面。而且,document对象是window对象的一个属性,因此可以将其作为全局对象来访问。
window.document === document // true
Document节点具有下列特征:
-
nodeType的值为:9
-
nodeName的值为:"#document"
-
nodeValue的值为:null
-
parentNode的值为:null
-
其子节点可能是一个DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction或Comment。
Document类型可以表示HTML页面或者其他基于XML的文档。不过,最常见的应用还是作为HTMLDocument实例的document对象。通过这个文档对象,不仅可以取得与页面有关的信息,而且还能操作页面的外观及其底层结构。
1.文档的子节点
虽然DOM标准规定Document节点的子节点可以是DocumentType、Element、ProcessingIn-struction或Comment,但还有两个内置的访问其子节点的快捷方式。第一个就是documentElement属性,该属性始终指向HTML页面中的元素。另一个就是通过childNodes列表访问文档元素,但通过documentElement属性则能更快捷、直接的访问元素。
这个页面经浏览器解析后,其文档中只包含一个子节点,即元素。可以通过documentElement或childNodes列表来访问这个元素。
var html = document.documentElement; // 取得对的引用
console.log(html === document.childNodes[0]); // true
console.log(html === document.firstChild); // true
documentElement、firstChild和childNodes[0]的值相同。,都指向元素。
作为HTMLDocument的实例,document对象还有一个body属性,直接指向
元素。因为开发人员经常要使用这个元素,所以document.body在JS代码汇总出现的频率很高。var body = document.body;
所有浏览器都支持document.documentElement和document.body属性。
Document另一个可能的子节点就是DocumentType。通常将标签看成一个与文档其他部分不同的实体,可以通过doctype属性来访问它的信息。
var doctype = document.doctype;
类似的,还有如下功能方法,取得文档信息。
取得文档标题
var originalTitle = document.title;
设置文档标题
document.title = "New page ttile";
网页请求相关方法;
- URL:属性中包含页面完整的URL(即地址栏中显示的URL)
- domain:只包含页面的域名
- referrer:保存链接到当前页面的页面的URL(从哪到这的),referrer属性可能包含空字符串。所有信息都存在请求HTTP头部,只不过通过这些属性让我们能够在JavaScript中访问它们而已。
//取得完整URL
var url = document.URL;
//取得域名
var domain = document.domain;
//取得来源页面的URL
var referrer = document.referrer;
URL和domain属性是相互关联的。例如,如果document.URL为http://www.wrox.com/WileyCDA/
,那么document.domain就是www.wrox.com
。
这三个属性中,只有domain是可以设置的,但由于安全方面限制,并非可以给domain设置任何值。如果URL包含一个子域名,例如p2p.wrox.com,那么就只能将domain设置为wrox.com
。不能将这个属性设置为URL中不包含的域。
// 假设页面来自:p2p.wrox.com
document.domain = "wrox.com"; // 成功
document.domain = "nczonline.net"; // 出粗!
当页面中包含来自其他子域的框架或内嵌框架时,能够设置domain就非常方便了。由于跨域安全限制,来自不同子域的页面无法通过JavaScript通信。而通过将每个页面的document.domain设置为相同的值,这些页面就可以互相访问对方包含的JavaScript对象了。例如,假设有一个页面加载自www.wrox.com,其中包含一个内嵌框架,框架内的页面加载来自p2p.wrox.com。由于document.domain字符串不一样,内外两个页面之间无法相互访问对方的JavaScript对象。但如果将两个页面的document.domain值设置为wrox.com,他们之间就可以通信了。
浏览器对domain属性还有一个限制,即如果域名一开始是松散的,那么不能将它设置为紧绷的。换句话说,在将document.domain设置为wrox.com之后,不能再将其设置回p2p.wrox.com,否认导致错误。
// 假设页面来自于p2p.wrox.com域
document.domain = "wrox.com"; // 松散的(成功)
document.domain = "p2p.wrox.com" // 紧绷的(出错!)
查找元素
Document类型提供的两个方法:
- getElementById()
- getElementsByTagName()
第一个方法,接受一个参数:要取得的元素ID,如果找到相应的元素则返回该元素,如果不存在带有相应的ID的元素,则返回null。注意,这里的ID必须与页面中的元素的ID特性严格匹配,包括大小写。
Document
但是,在IE7下和更早版本的浏览器都将返回null。
var div = document.getElementById("mydiv");
IE8及较早版本不区分ID的大小写,因此"myDiv"和"mydiv"会被当作相同的元素ID。
如果页面中多个元素的ID值相同,getElmentById()只返回文档中第一次出现的元素。
document.getElementsByTagName()
接受一个参数,既要取得元素的标签名,返回的是包含零或多个元素的Nodelist。在HTML文档中,这个方法会返回一个HTMLCollection对象,作为一个动态集合,该对象与Nodelist对象非常类似。例如,下列代码回取得页面中所有的元素,并返回一个HTMLCollection。
var images = document.getElementsByTagName("img");
这行代码会将一个HTMLCollection对象保存在images变量中。与Nodelist对象类似,可以使用方括号语法或item()方法来访问对象中的项。而这个对象中元素的数量则可以通过其length属性取得。
Document
HTMLCollection对象还有一个方法,叫做namedItem(),使用这个方法可以通过元素的那么name特性取得集合中的项。例如,假设上面提到的页面中包含如下
那么可以通过如下方式从divs变量中取得这个元素。
Document
咋提供按索引访问项的基础上,HTMLCollection还支持按名称访问项,这就为我们取得实际想要的元素提供了便利。而且,对命名的项也可以使用方括号语法来访问。
Document
第三个方法,也就是只有HTMLDocument类型才有的方法,是getElementsByName(),顾名思义,方法会返回带有给定name特性的所有元素。最常使用getElementsByName()方法的情况是取得单选按钮。
Document
所有的单选按钮的name特性值相同都是color,但它们的ID不同,ID作用在于将label元素应用到每个单选按钮,而name特性则用以确保三个值中只有一个被发送给浏览器。这样,我们取得所有单选按钮。
特殊集合
除了属性和方法,document对象还有一些特殊的集合,这些集合都是HTMLCollection对象,为访问文档常用的部分提供了快捷方式,包括:
- document.anchors,包含文档中所有带name特性的元素
- document.applets,包含文档中所有的
- document.forms,包含所有的
- document.images,包含所有的元素
- document.links,包含文档中所有的带href特性的元素。
Element类型
访问元素的标签名,使用nodeName属性,也可以使用tagName属性;这两个属性会返回相同的值,使用后者主要是为了清晰可见。
可以先这样取得元素及其标签名:
Document
所有的HTML元素都由HTMLElement类型表示,不是直接通过这个类型,也是通过它的子类型表示。HTMLElement类型直接继承自Element并添加了一些属性。添加的这些属性分别对应于每个HTML元素中都存在的下列标准特性。
- id,元素在文档中的唯一标识符
- title,元素附加说明信息,通过工具提示条显示
- lang,元素语言代码
- dir,语言方向,ltr(left to right),rtl(right to left),很少使用。
- className,与元素class属性对应。
Document
也可以为每个属性赋值,修改对应属性的特性。
Document
*取得特性
getAttribute()
Document
传递给方法的特性名与实际的特性名相同,因此要想得到class的特性值,应该传入class而不是className,后者只有在通过对象属性访问特性时采用。如果给定名称特性名不存在,则返回null。
当然,你可以添加自定义特性,标准HTML中没有的特性。
这个元素包含一个名为my_special_attrbute
的自定义特性,值为hello!
,可以像取得其他特性一样取得这个值。
Document
不过,只有公认的特性才会以属性的形式添加到DOM对象中
因为id和align在HTML中是
的公认特性,因此该元素的DOM对象中也将存在对应的属性。不过,自定义特性my_special_attribute
在Safari、Opera、Chrome及Firefox是不存在的;但IE却会为自定义特性也创建属性。
console.log(div.id); // "myDiv"
console.log(div.my_special_attribute); // undefined(IE除外)
console.log(div.align); // "left"
有两类特殊的特性,它们虽然有对应的属性名,但属性值与通过getAttribute()返回的值并不相同,第一类是style,通过CSS为元素指定形式。通过getAttribute()访问时,返回的style特性值中包含的是CSS文本,而通过属性来访问它则返回一个对象。由于style属性用于以编程方式访问元素样式的,因此并没有映射到style属性。
第二类与众不同的特性时onclick这样的事件处理程序,当在元素上使用时,onclick特性中包含的是JavaScript代码,如果通过getAttribute()访问,则会返回相应的代码的字符串。而在访问onclick属性时,则会返回一个JavaScript函数(如果未指定,返回null)这是因为onclick及其他事件处理程序属性本身就应该被赋予函数值。
由于这些差别,通过JavaScript编程方式操作DOM时,开发者经常不使用getAttribute(),而是只使用对象属性,只有在取得自定义特性值情况下,才会使用getAttribute()方法。
Document
设置特性
与getAttribute()对应的方法是setAttribute(),这个方法接受两个参数:要设置的特性名和值。如果特性已经存在,setAttribute()会以指定的值替换现有的值;如果特性不存在,setAttribute()则创建该属性并设置相应的值。
Document
通过方法既可以操作HTML特性,也可以操作自定义特性。通过这个方法设置的特性名会统一转换为小写形式,即ID最终会成为id。
不过,添加自定义属性,该属性不会自动成为元素的特性。
Document
attributes属性
Element类型是使用attributes属性的唯一一个DOM节点类型。attributes属性中包含一个NamedNodeMap,与Nodelist类似,也是一个动态集合。元素的每一个特性都由一个Attr节点表示,每个节点都保存在NamedNodeMap对象中。NamedNodeMap拥有下列方法:
- getNamedItem(name):返回nodeName属性等于name的节点;
- removeNamedItem(name):从列表中移除nodeName属性等于name的节点;
- setNamedItem(node):向列表中添加节点,以节点的nodeName属性为索引;
- item(pos):返回位于数字pos位置处的节点。
attributes属性中包含一系列节点,每个节点的nodeName就是特性的名称,而节点的nodeValue就是特性的值。要取得元素的id特性,可以:
Document
或者使用方括号语法通过特性名称访问节点的简写方式。
var id = div.attributes["id"].nodeValue;
也可以通过以上的方法设置特性的值。
div.attributes["id"].nodeValue = "someOtherId";
一般来说,attributes的方法不够方便,更多的会使用getAttribute()、removeAttribute()和setAttribute()方法。
不过,如果想要遍历元素特性,attributes属性可以派上用场。在需要将DOM结构序列化为XML或者HTML字符串时,多数都会涉及遍历元素特性。
Document
针对attributes对象中的特性,不同浏览器返回的顺序不同。这些特性在XML和HTML代码中出现的先后顺序,不一定与它们出现在attributes对象中的顺序一致。
IE7及更早版本会返回HTML元素中所有可能的特性,包括没有指定的特性。换句话说,返回100多个特性的情况会很常见。
创建元素
var div = document.createElement("div");
使用createElement()方法创建新元素的同时,也为新元素设置了ownerDocument属性。此时,还可以操作元素的特性,为它添加更多子节点,以及执行其他操作。
div.id = "myNewDiv";
div.className = "box";
然后添加到文档数中。可以使用appendChild()、insertBefore()、replaceChild()方法。
document.body.appendChild(div);