DOM(Document Object Model)

参考书:《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的返回值。


DOM(Document Object Model)_第1张图片
from MDN

所以,对于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

DOM(Document Object Model)_第2张图片

可以看到其中childNodes属性里,Nodelist对象的传承,包含了从根目录下的所有节点。
其中,#div的Nodelist里,是否发现了有5个节点,但实际上源代码只有2个元素啊,其实[0],[2],[4]所代表的#text是我们源代码换行时留下的空位,在Nodelist里就用#text来表示了。

i'm P

i'm H1


修改后,将只有两个element。

注意,length属性表示的是访问Nodelist的那一刻,其中包含的节点数量。
对于JS函数内部类数组对象arguments对象使用Array.prototype.slice()方法可以将其转换为数组。而采用同样的方法,也可以将Nodelist对象转换为数组。

test6




    
    Fuck!


    

i'm P

i'm H1

DOM(Document Object Model)_第3张图片

为方便演示,缩小范围,只抽取了#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属性,可以访问同一列表中的其他节点。

关于节点关系,主要就是子节点、节点、父节点之间的定位关系。


DOM(Document Object Model)_第4张图片

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


    
Which color do you prefer?

所有的单选按钮的name特性值相同都是color,但它们的ID不同,ID作用在于将label元素应用到每个单选按钮,而name特性则用以确保三个值中只有一个被发送给浏览器。这样,我们取得所有单选按钮。

特殊集合

除了属性和方法,document对象还有一些特殊的集合,这些集合都是HTMLCollection对象,为访问文档常用的部分提供了快捷方式,包括: