深入淺出DOM基礎(chǔ)——《DOM探索之基礎(chǔ)詳解篇》學(xué)習(xí)筆記

之前通過深入學(xué)習(xí)DOM的相關(guān)知識(shí),看了慕課網(wǎng)DOM探索之基礎(chǔ)詳解篇這個(gè)視頻(在最近看第三遍的時(shí)候,準(zhǔn)備記錄一點(diǎn)東西,算是對(duì)自己學(xué)習(xí)的一點(diǎn)總結(jié))凌彬,對(duì)DOM的理解又具體了一步,因?yàn)镈OM本來(lái)就是一個(gè)抽象和概念性的東西循衰,每深入一步了解铲敛,在腦中就會(huì)稍微具體一點(diǎn),通過這次的對(duì)DOM的系統(tǒng)學(xué)習(xí)会钝,對(duì)DOM有一個(gè)比較深刻的理解伐蒋,明白了DOM在JavaScript這門語(yǔ)言中舉足輕重的地位,了解了DOm的發(fā)展歷史迁酸,也讓我明白了存在瀏覽器瀏覽器兼容性的歷史原因先鱼,對(duì)DOM的結(jié)構(gòu)有了進(jìn)一步的認(rèn)知,對(duì)DOM的一些API也更加熟悉奸鬓,對(duì)比較抽象和概念性的DOM認(rèn)知稍微具體了一些焙畔。下面就是自己深入學(xué)習(xí)DOM這門課程整理的一些筆記,大部分來(lái)自學(xué)習(xí)中查閱的資料以及視頻中老師講的一些關(guān)鍵性知識(shí)點(diǎn),當(dāng)然也不可或缺的有自己的一些記錄和理解串远。

原文收錄在我的 GitHub博客 (https://github.com/jawil/blog) 宏多,喜歡的可以關(guān)注最新動(dòng)態(tài)儿惫,大家一起多交流學(xué)習(xí),共同進(jìn)步绷落,以學(xué)習(xí)者的身份寫博客姥闪,記錄點(diǎn)滴。

文章稍長(zhǎng)砌烁,本文只論述DOM基礎(chǔ)概念,不涉及DOM的一些事件原理機(jī)制催式,頁(yè)面元素的操作和常用API的講解以及兼容性事項(xiàng)函喉,所以概念性東西比較多,稍微有點(diǎn)抽象荣月,其中有筆記來(lái)大部分來(lái)自老師的口述管呵,還有一部分是查閱的文檔,最后有一部分是自己的記錄和理解哺窄。

通過document.createElement("p")創(chuàng)建一個(gè)p元素一共溯尋了7層原型鏈捐下,你知道嗎?

學(xué)習(xí)視頻地址:DOM探索之基礎(chǔ)詳解篇萌业,老師講的很好坷襟,有興趣的可以結(jié)合視頻學(xué)習(xí)一下,建議看完視頻再看筆記生年,加深印象婴程,你會(huì)受益匪淺。

1抱婉、什么是DOM?

DOM档叔,文檔對(duì)象模型(Document Object Model)。DOM是 W3C(萬(wàn)維網(wǎng)聯(lián)盟)的標(biāo)準(zhǔn)蒸绩,DOM定義了訪問HTML和XML文檔的標(biāo)準(zhǔn)衙四。在W3C的標(biāo)準(zhǔn)中,DOM是獨(dú)于平臺(tái)和語(yǔ)言的接口患亿,它允許程序和腳本動(dòng)態(tài)地訪問和更新文檔的內(nèi)容传蹈、結(jié)構(gòu)和樣式。

W3C DOM由以下三部分組成:

  • 核心DOM - 針對(duì)任何結(jié)構(gòu)化文檔的標(biāo)準(zhǔn)模型
  • XML DOM - 針對(duì) XML 文檔的標(biāo)準(zhǔn)模型
  • HTML DOM - 針對(duì) HTML 文檔的標(biāo)準(zhǔn)模型

DOM(文檔對(duì)象模型)是針對(duì)xml經(jīng)過擴(kuò)展用于html的應(yīng)用程序編程接口窍育,我們又叫API卡睦。DOM把整個(gè)頁(yè)面映射為一個(gè)多層的節(jié)點(diǎn)結(jié)構(gòu),html或xml頁(yè)面中的每個(gè)組成部分都是某種類型的節(jié)點(diǎn)漱抓,這些節(jié)點(diǎn)又包含著不同類型的數(shù)據(jù)表锻。

2、DOM的地位

我們知道乞娄,一個(gè)網(wǎng)頁(yè)是由html來(lái)搭建結(jié)構(gòu)的瞬逊,通過css來(lái)定義網(wǎng)頁(yè)的樣式显歧,而JavaScript賦予了頁(yè)面的行為,通過它我們可以與頁(yè)面進(jìn)行交互确镊,實(shí)現(xiàn)頁(yè)面的動(dòng)畫效果等等士骤。那javascript究竟通過什么來(lái)實(shí)現(xiàn)的呢?通過ECMAScript這個(gè)標(biāo)準(zhǔn)蕾域,我們可以編寫程序讓瀏覽器來(lái)解析拷肌,利用ECMAScript,我們可以通過BOM對(duì)象(即browser object model)來(lái)操作瀏覽器窗口旨巷、瀏覽器導(dǎo)航對(duì)象(navigator)巨缘、屏幕分辨率(screen)、瀏覽器歷史(history)采呐、cookie等等若锁。但這個(gè)通過BOM來(lái)實(shí)現(xiàn)的交互遠(yuǎn)遠(yuǎn)不夠。要實(shí)現(xiàn)頁(yè)面的動(dòng)態(tài)交互和效果斧吐,操作html才是核心又固。那如何操作html呢?對(duì)煤率,就是DOM仰冠,簡(jiǎn)單的說,DOM給我們提供了用程序來(lái)動(dòng)態(tài)控制html的接口涕侈,也就是早期的DHTMl的概念沪停。因此,DOM處在javascript賦予html具備動(dòng)態(tài)交互和效果的能力的核心地位上裳涛。

3木张、DOM的發(fā)展-DOM0、DOM1端三、DOM2舷礼、DOM3的區(qū)別

3.1、DOM0

JavaScript在早期版本中提供了查詢和操作Web文檔的內(nèi)容API(如:圖像和表單)郊闯,在JavaScript中定義了定義了'images'妻献、'forms'等,因此我們可以像下這樣訪問第一張圖片或名為“user”的表單:

document.images[0]document.forms['user']

這實(shí)際上是未形成標(biāo)準(zhǔn)的試驗(yàn)性質(zhì)的初級(jí)階段的DOM团赁,現(xiàn)在習(xí)慣上被稱為DOM0育拨,即:第0級(jí)DOM。由于DOM0在W3C進(jìn)行標(biāo)準(zhǔn)備化之前出現(xiàn)欢摄,還處于未形成標(biāo)準(zhǔn)的初期階段熬丧,這時(shí)Netscape和Microsoft各自推出自己的第四代瀏覽器,自此DOM遍開始出各種問題怀挠。

3.2析蝴、DOM0與DHTML

Netscape Navigator 4和IE4分別發(fā)布于1997年的6月和10月害捕,這兩種瀏覽器都大幅擴(kuò)展了DOM,使JavaScript的功能大大增加闷畸,而此時(shí)也開始出現(xiàn)一個(gè)新名詞:DHTML尝盼。

DHTML是Dynamic HTML(動(dòng)態(tài)HTML)的簡(jiǎn)稱。DHTML并不是一項(xiàng)新技術(shù)佑菩,而是將HTML盾沫、CSS、JavaScript技術(shù)組合的一種描述殿漠。即:

  • 利用HTML把網(wǎng)頁(yè)標(biāo)記為各種元素
  • 利用CSS設(shè)置元素樣式及其顯示位置
  • 利用JavaScript操控頁(yè)面元素和樣式

利用DHTML疮跑,看起來(lái)可以很容易的控制頁(yè)面元素,并實(shí)現(xiàn)一此原本很復(fù)雜的效果(如:通過改變?cè)匚恢脤?shí)現(xiàn)動(dòng)畫)凸舵。但事實(shí)并非如此,因?yàn)闆]有規(guī)范和標(biāo)準(zhǔn)失尖,兩種瀏覽器對(duì)相同功能的實(shí)現(xiàn)確完全不一樣啊奄。為了保持程序的兼容性,程序員必須寫一些探查代碼以檢測(cè)JavaScript是運(yùn)行于哪種瀏覽器之下掀潮,并提供與之對(duì)應(yīng)的腳本菇夸。JavaScript陷入了前所未有的混亂,DHTML也因此在人們心中留下了很差的印象仪吧。

我們?cè)陂喿xDOM標(biāo)準(zhǔn)的時(shí)候庄新,經(jīng)常會(huì)看到DOM0級(jí)這樣的字眼,實(shí)際上DOM0級(jí)這個(gè)標(biāo)準(zhǔn)是不存在的薯鼠。所謂DOM0級(jí)只是DOM
歷史坐標(biāo)系中的一個(gè)參照點(diǎn)而已择诈,具體地說DOM0級(jí)就是指IE4.0和Netscape navigator4.0最初支持的那個(gè)DHTML。

3.3出皇、DOM1的出現(xiàn)

在瀏覽器廠商進(jìn)行瀏覽器大站的同時(shí)羞芍,W3C結(jié)合大家的優(yōu)點(diǎn)推出了一個(gè)標(biāo)準(zhǔn)化的DOM,并于1998年10月完成了第一級(jí) DOM郊艘,即:DOM1荷科。W3C將DOM定義為一個(gè)與平臺(tái)和編程語(yǔ)言無(wú)關(guān)的接口,通過這個(gè)接口程序和腳本可以動(dòng)態(tài)的訪問和修改文檔的內(nèi)容纱注、結(jié)構(gòu)和樣式畏浆。

DOM1級(jí)主要定義了HTML和XML文檔的底層結(jié)構(gòu)。在DOM1中狞贱,DOM由兩個(gè)模塊組成:DOM Core(DOM核心)和DOM HTML刻获。其中,DOM Core規(guī)定了基于XML的文檔結(jié)構(gòu)標(biāo)準(zhǔn)斥滤,通過這個(gè)標(biāo)準(zhǔn)簡(jiǎn)化了對(duì)文檔中任意部分的訪問和操作将鸵。DOM HTML則在DOM核心的基礎(chǔ)上加以擴(kuò)展勉盅,添加了針對(duì)HTML的對(duì)象和方法,如:JavaScript中的Document對(duì)象.

3.4顶掉、DOM2

在DOM1的基礎(chǔ)上DOM2引入了更多的交互能力草娜,也支持了更高級(jí)的XML特性。DOM2將DOM分為更多具有聯(lián)系的模塊痒筒。DOM2級(jí)在原來(lái)DOM的基礎(chǔ)上又?jǐn)U充了鼠標(biāo)宰闰、用戶界面事件、范圍簿透、遍歷等細(xì)分模塊移袍,而且通過對(duì)象接口增加了對(duì)CSS的支持。DOM1級(jí)中的DOM核心模塊也經(jīng)過擴(kuò)展開始支持XML命名空間老充。在DOM2中引入了下列模塊葡盗,在模塊包含了眾多新類型和新接口:

  • DOM視圖(DOM Views):定義了跟蹤不同文檔視圖的接口
  • DOM事件(DOM Events):定義了事件和事件處理的接口
  • DOM樣式(DOM Style):定義了基于CSS為元素應(yīng)用樣式的接口
  • DOM遍歷和范圍(DOM Traversal and Range):定義了遍歷和操作文檔樹的接口

完整的DOM2標(biāo)準(zhǔn)(圖片來(lái)自百度百科):

3.5、DOM3

DOM3級(jí):進(jìn)一步擴(kuò)展了DOM啡浊,引入了以統(tǒng)一方式加載和保存文檔的方法觅够,它在DOM Load And Save這個(gè)模塊中定義;同時(shí)新增了驗(yàn)證文檔的方法巷嚣,是在DOM Validation這個(gè)模塊中定義的喘先。
DOM3進(jìn)一步擴(kuò)展了DOM,在DOM3中引入了以下模塊:

  • DOM加載和保存模塊(DOM Load and Save):引入了以統(tǒng)一方式加載和保存文檔的方法
  • DOM驗(yàn)證模塊(DOM Validation):定義了驗(yàn)證文檔的方法
  • DOM核心的擴(kuò)展(DOM Style):支持XML 1.0規(guī)范廷粒,涉及XML Infoset窘拯、XPath和XML Base

4、認(rèn)識(shí)DOM

DOM可以將任何HTML描繪成一個(gè)由多層節(jié)點(diǎn)構(gòu)成的結(jié)構(gòu)坝茎。節(jié)點(diǎn)分為12種不同類型涤姊,每種類型分別表示文檔中不同的信息及標(biāo)記。每個(gè)節(jié)點(diǎn)都擁有各自的特點(diǎn)景东、數(shù)據(jù)和方法砂轻,也與其他節(jié)點(diǎn)存在某種關(guān)系。節(jié)點(diǎn)之間的關(guān)系構(gòu)成了層次斤吐,而所有頁(yè)面標(biāo)記則表現(xiàn)為一個(gè)以特定節(jié)點(diǎn)為根節(jié)點(diǎn)的樹形結(jié)構(gòu)搔涝。

先看一張w3school上面的一張圖:

先來(lái)看看下面代碼:

<!DOCTYPE html>
  <html>
  <head>
     <meta charset="utf-8">
      <title>DOM</title>
  </head>
  <body>
      <h2><a >javascript DOM</a></h2>
      <p>對(duì)HTML元素進(jìn)行操作,可添加和措、改變或移除css樣式等</p>
      <ul>
          <li>Javascript</li>
          <li>DOM</li>
          <li>CSS</li>
      </ul>
  </body>
  </html>

將HTML代碼分解為DOM節(jié)點(diǎn)層次圖:

HTML文檔可以說由節(jié)點(diǎn)構(gòu)成的集合庄呈,DOM節(jié)點(diǎn)有:

  1. 元素節(jié)點(diǎn):上圖中<html>、<body>派阱、<p>等都是元素節(jié)點(diǎn)诬留,即標(biāo)簽。
  2. 文本節(jié)點(diǎn):向用戶展示的內(nèi)容,如<li>...</li>中的JavaScript文兑、DOM盒刚、CSS等文本。
  3. 屬性節(jié)點(diǎn):元素屬性绿贞,如<a>標(biāo)簽的鏈接屬性因块。

5、文檔類型發(fā)展史

我們說DOM文檔對(duì)象模型是從文檔中抽象出來(lái)的籍铁,DOM操作的對(duì)象也是文檔涡上,因此我們有必要了解一下文檔的類型歌懒。文檔隨著歷史的發(fā)展演變?yōu)槎喾N類型衙耕,如下:

5.1、GML

GML(Generalized Markup Language, 通用標(biāo)記語(yǔ)言)是1960年代的一種IBM文檔格式化語(yǔ)言极阅,用于描述文檔的組織結(jié)構(gòu)增显、各部件及其相互關(guān)系雁佳。GML在文檔具體格式方面,為文檔員提供了一些方便同云,他們不必再為IBM的打印機(jī)格式化語(yǔ)言SCRIPT要求的字體規(guī)范甘穿、行距以及頁(yè)面設(shè)計(jì)等浪費(fèi)精力。這個(gè)IBM的GML包括1960年代的GML和1980年代的ISIL梢杭。

5.2、SGML

SGML(Standard Generalized Markup Language, 標(biāo)準(zhǔn)通用標(biāo)記語(yǔ)言)是1986年基于IBM的GML制定ISO標(biāo)準(zhǔn)(ISO 8879)秸滴。SGML是現(xiàn)時(shí)常用的超文本格式的最高層次標(biāo)準(zhǔn)武契,是可以定義標(biāo)記語(yǔ)言的元語(yǔ)言,甚至可以定義不必采用"<>"的常規(guī)方式荡含。由于SGML的復(fù)雜咒唆,因而難以普及。HTML和XML同樣衍生于SGML释液,XML可以被認(rèn)為是SGML的一個(gè)子集全释,而HTML是SGML的一個(gè)應(yīng)用。

5.3误债、HTML

HTML(HyperText Markup Language, 超文本標(biāo)記語(yǔ)言)是為“網(wǎng)頁(yè)創(chuàng)建和其它可在網(wǎng)頁(yè)瀏覽器中看到的信息”設(shè)計(jì)的一種標(biāo)記語(yǔ)言浸船。HTML被用來(lái)結(jié)構(gòu)化信息——例如標(biāo)題、段落和列表等等寝蹈,也可用來(lái)在一定程度上描述文檔的外觀和語(yǔ)義李命。1982年,蒂姆·伯納斯-李為使世界各地的物理學(xué)家能夠方便的進(jìn)行合作研究箫老,創(chuàng)建了使用于其系統(tǒng)的HTML封字。之后HTML又不斷地?cái)U(kuò)充和發(fā)展,成為國(guó)際標(biāo)準(zhǔn),由萬(wàn)維網(wǎng)聯(lián)盟(W3C)維護(hù)阔籽。第一個(gè)正式標(biāo)準(zhǔn)是1995年發(fā)布的RFC 1866(HTML 2.0)流妻。

5.4、XML

XML(eXtensible Markup Language, 可擴(kuò)展標(biāo)記語(yǔ)言)是專家們使用SGML精簡(jiǎn)制作笆制,并依照HTML的發(fā)展經(jīng)驗(yàn)绅这,產(chǎn)生出一套使用上規(guī)則嚴(yán)謹(jǐn),但是簡(jiǎn)單的描述數(shù)據(jù)語(yǔ)言项贺。XML在1995年開始有雛形君躺,在1998二月發(fā)布為W3C的標(biāo)準(zhǔn)(XML1.0)

5.5、XHTML

XHTML(eXtensible HyperText Markup Language, 可擴(kuò)展超文本標(biāo)記語(yǔ)言)的表現(xiàn)方式與超文本標(biāo)記語(yǔ)言(HTML)類似开缎,不過語(yǔ)法上更加嚴(yán)格。從繼承關(guān)系上講奕删,HTML是一種基于標(biāo)準(zhǔn)通用標(biāo)記語(yǔ)言(SGML)的應(yīng)用俺泣,是一種非常靈活的置標(biāo)語(yǔ)言,而XHTML則基于可擴(kuò)展標(biāo)記語(yǔ)言(XML)完残,XML是SGML的一個(gè)子集伏钠。XHTML 1.0在2000年1月26日成為W3C的推薦標(biāo)準(zhǔn)。

6谨设、DOM節(jié)點(diǎn)類型

DOM1級(jí)定義了一個(gè)Node接口熟掂,這個(gè)Node接口在javascript中是作為Node類型來(lái)實(shí)現(xiàn)的。除了IE以外扎拣,其他所有瀏覽器都可以訪問這個(gè)類型赴肚。每個(gè)節(jié)點(diǎn)都有一個(gè)nodeType屬性,用于表明節(jié)點(diǎn)的類型二蓝。節(jié)點(diǎn)類型通過定義數(shù)值常量和字符常量?jī)煞N方式來(lái)表示誉券,IE只支持?jǐn)?shù)值常量。節(jié)點(diǎn)類型一共有12種刊愚,這里介紹常用的7種類型踊跟。如下圖:

看下面這個(gè)例子:

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>DocumentFragment文檔片段節(jié)點(diǎn)</title>  
</head>  
<body> 
<!-- tip區(qū)域 -->
    <div id="tip">test1</div> 
    <ul class="list-node">
    <li>test2<li>
    </ul>  
    <script>  
        var frag = document.createDocumentFragment();  
        for (var i = 0; i < 10; i++) {  
            var li = document.createElement("li");  
            li.innerHTML = "List item" + i;  
            frag.appendChild(li);  
        }  
        document.getElementById("list-node").appendChild(frag);  
    </script>  
</body>  
</html>  

以下引用均來(lái)自老師說的話,感覺每句話都很重要鸥诽,所以就寫下來(lái)了商玫。

(1)Element(元素節(jié)點(diǎn)):

是組成文檔樹的重要部分,它表示了html牡借、xml文檔中的元素决帖。通常元素因?yàn)橛凶釉亍⑽谋竟?jié)點(diǎn)或者兩者的結(jié)合蓖捶,元素節(jié)點(diǎn)是唯一能夠擁有屬性的節(jié)點(diǎn)類型地回。

例子中的:htmlheademeta刻像、title畅买、bodydiv细睡、ul谷羞、liscript都屬于Element(元素節(jié)點(diǎn));

(2)Attr(屬性節(jié)點(diǎn)):

代表了元素中的屬性溜徙,因?yàn)閷傩詫?shí)際上是附屬于元素的湃缎,因此屬性節(jié)點(diǎn)不能被看做是元素的子節(jié)點(diǎn)。因而在DOM中屬性沒有被認(rèn)為是文檔樹的一部分蠢壹。換句話說嗓违,屬性節(jié)點(diǎn)其實(shí)被看做是包含它的元素節(jié)點(diǎn)的一部分,它并不作為單獨(dú)的一個(gè)節(jié)點(diǎn)在文檔樹中出現(xiàn)图贸。

例子中的:lang蹂季、charsetid疏日、class都屬于Attr(屬性節(jié)點(diǎn));

(3)Text(文本節(jié)點(diǎn)):

是只包含文本內(nèi)容的節(jié)點(diǎn)偿洁,在xml中稱為字符數(shù)據(jù),它可以由更多的信息組成沟优,也可以只包含空白涕滋。在文檔樹中元素的文本內(nèi)容和屬性的文本內(nèi)容都是由文本節(jié)點(diǎn)來(lái)表示的。

例子中的:DocumentFragment文檔片段節(jié)點(diǎn)挠阁、test1何吝、test2元素節(jié)點(diǎn)之后的空白區(qū)域都屬于Text(文本節(jié)點(diǎn));

(4)Comment(注釋節(jié)點(diǎn)):

表示注釋的內(nèi)容

例子中的:``都屬于Comment(注釋節(jié)點(diǎn));

(5)Document(文檔節(jié)點(diǎn)) :

是文檔樹的根節(jié)點(diǎn)鹃唯,它是文檔中其他所有節(jié)點(diǎn)的父節(jié)點(diǎn)。要注意的是瓣喊,文檔節(jié)點(diǎn)并不是html坡慌、xml文檔的根元素,因?yàn)樵趚ml文檔中藻三,處理指令洪橘、注釋等內(nèi)容可以出現(xiàn)在根元素之外,所以我們?cè)跇?gòu)造DOM樹的時(shí)候棵帽,根元素并不適合作為根節(jié)點(diǎn)熄求,因此就有了文檔節(jié)點(diǎn),而根元素是作為文檔節(jié)點(diǎn)的子節(jié)點(diǎn)出現(xiàn)的逗概。

例子中的:<!DOCTYPE html>弟晚、html作為Document(文檔節(jié)點(diǎn))的子節(jié)點(diǎn)出現(xiàn);

(6)DocumentType(文檔類型節(jié)點(diǎn)):

每一個(gè)Document都有一個(gè)DocumentType屬性,它的值或者是null,或者是DocumentType對(duì)象卿城。比如聲明文檔類型時(shí)<!doctype html>就是文檔類型節(jié)點(diǎn)枚钓。

例子中的:<!DOCTYPE html> 就屬于DocumentType(文檔類型節(jié)點(diǎn));

(7)DocumentFragment(文檔片段節(jié)點(diǎn)):

是輕量級(jí)的或最小的Document對(duì)象,它表示文檔的一部分或者是一段瑟押,不屬于文檔樹搀捷。不過它有一種特殊的行為,該行為使得它非常有用多望。比如:當(dāng)請(qǐng)求把一個(gè)DocumentFragment節(jié)點(diǎn)插入到文檔的時(shí)候嫩舟,插入的不是DocumentFragment自身,而是它的所有的子孫節(jié)點(diǎn)怀偷。這使得DocumentFragment成了有用的占位符家厌,暫時(shí)存放那些一次插入文檔的節(jié)點(diǎn),同時(shí)它還有利于實(shí)現(xiàn)文檔的剪切枢纠、復(fù)制和粘貼等操作像街。

例子中的:var frag = document.createDocumentFragment();就屬于DocumentFragment(文檔片段節(jié)點(diǎn));

7、DOM的nodeType晋渺、nodeName镰绎、nodeValue

7.1 nodeType

通過DOM節(jié)點(diǎn)類型,我們可知木西,可以通過某個(gè)節(jié)點(diǎn)的nodeType屬性來(lái)獲得節(jié)點(diǎn)的類型畴栖,節(jié)點(diǎn)的類型可以是數(shù)值常量或者字符常量。示例代碼如下:

<!DOCTYPE html>  
<html>  
<head lang="en">  
    <meta charset="UTF-8">  
    <title>nodeType</title>  
</head>  
<body>  
    <div id="container">這是一個(gè)元素節(jié)點(diǎn)</div>  
    <script>  
        var divNode = document.getElementById('container');  
        /*
        IE中只支持?jǐn)?shù)值常量八千,因?yàn)榈桶姹綢E瀏覽器沒有內(nèi)置Node對(duì)象吗讶,其他瀏覽器數(shù)值常量和字符常量都支持,因此可
        以直接用數(shù)值常量判斷恋捆,這里為了比較兩種寫法照皆,便都寫在了這里  
        */  
        if (divNode.nodeType == Node.ELEMENT_NODE || divNode.nodeType === 1) {  
            alert("Node is an element.");  
        }         
    </script>  
</body>  
</html>  

7.2 nodeName和nodeValue

先看示例代碼:

<!DOCTYPE html>  
<html>  
<head lang="en">  
    <meta charset="UTF-8">  
    <title>nodeName,nodeValue</title>  
</head>  
<body>  
    <!--nodeName,nodeValue實(shí)驗(yàn)-->  
    <div id="container">這是一個(gè)元素節(jié)點(diǎn)</div>  
    <script>  
        var divNode = document.getElementById('container');  
        console.log(divNode.nodeName + "/" + divNode.nodeValue);     
        //結(jié)果:    DIV/null  
        
        var attrNode = divNode.attributes[0];  
        console.log(attrNode.nodeName + "/" + attrNode.nodeValue);      
        //結(jié)果:   id/container  
        
        var textNode = divNode.childNodes[0];  
        console.log(textNode.nodeName + "/" + textNode.nodeValue);      
        //結(jié)果:   #text/這是一個(gè)元素節(jié)點(diǎn)  
        
        var commentNode = document.body.childNodes[1];  
        //表示取第二個(gè)注釋節(jié)點(diǎn),因?yàn)閎ody下面的第一個(gè)注釋節(jié)點(diǎn)為空白符沸停。  
        console.log(commentNode.nodeName + "/" +commentNode.nodeValue);  
        //結(jié)果:  #comment/nodeName,nodeValue實(shí)驗(yàn)  
        
        console.log(document.doctype.nodeName + "/" + document.doctype.nodeValue);   
        //結(jié)果: html/null  
        
        var frag = document.createDocumentFragment();  
        console.log(frag.nodeName + "/" + frag.nodeValue);    
        //結(jié)果: #document-fragment/null  
    </script>  
</body>  
</html>  

根據(jù)實(shí)驗(yàn)膜毁,得出以下匯總表格:

8、domReady

還記得剛開始學(xué)習(xí)JavaScript時(shí)候愤钾,經(jīng)常會(huì)犯這樣的錯(cuò)誤:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Dom not ready</title>
    <script>
      document.getElementById("header").style.color = "red";
    </script>
  </head>
  <body>
    <h1 id="header">這里是h1元素包含的內(nèi)容</h1>
  </body>
</html>

最后發(fā)現(xiàn)結(jié)果并不是我們想要的瘟滨,文字并沒有變成紅色,我想最先入門學(xué)習(xí)JavaScript操作DOM時(shí)候多多少少會(huì)遇到這種困惑和錯(cuò)誤能颁,其實(shí)出現(xiàn)這種問題的原因就是我們沒有區(qū)分HTML標(biāo)簽和DOM節(jié)點(diǎn)的區(qū)別的緣故了,由這個(gè)問題就引出下面要說的domReady和瀏覽器渲染解析原理了杂瘸。

8.1、什么是domReady伙菊?

html是一種標(biāo)記語(yǔ)言败玉,它告訴我們這個(gè)頁(yè)面有什么內(nèi)容敌土,但行為交互是需要通過DOM操作來(lái)實(shí)現(xiàn)的。我們不要以為有兩個(gè)尖括號(hào)就以為它是一個(gè)DOM了绒怨,html標(biāo)簽要通過瀏覽器解析才會(huì)變成DOM節(jié)點(diǎn)纯赎,當(dāng)我們向地址欄傳入一個(gè)url的時(shí)候,我們開始加載頁(yè)面南蹂,就能看到內(nèi)容犬金,在這期間就有一個(gè)DOM節(jié)點(diǎn)構(gòu)建的過程。節(jié)點(diǎn)是以樹的形式組織的六剥,當(dāng)頁(yè)面上所有的html都轉(zhuǎn)換為節(jié)點(diǎn)以后晚顷,就叫做DOM樹構(gòu)建完畢,簡(jiǎn)稱為domReady疗疟。

8.2该默、那么瀏覽器是如何將html標(biāo)簽解析變成DOM節(jié)點(diǎn)的呢?

實(shí)際上瀏覽器是通過渲染引擎來(lái)實(shí)現(xiàn)的策彤。渲染引擎的職責(zé)就是把請(qǐng)求的內(nèi)容顯示到瀏覽器屏幕上栓袖。默認(rèn)情況下渲染引擎可以顯示html、xml文檔及圖片店诗。通過插件(瀏覽器擴(kuò)展)它可以顯示其他類型的文檔裹刮,比如我們安裝pdf viewer插件,我們就可以顯示pdf文檔庞瘸。這里專注渲染引擎的主要用途捧弃,即是將css格式化的html和圖片在瀏覽器上進(jìn)行顯示。

8.3擦囊、瀏覽器渲染引擎的基本渲染流程

瀏覽器渲染要做的事就是把CSS,HTML违霞,圖片等靜態(tài)資源展示到用戶眼前。

渲染引擎首先通過網(wǎng)絡(luò)獲得所請(qǐng)求文檔的內(nèi)容瞬场,通常以8k分塊的方法來(lái)完成:

上圖就是html渲染的基本過程买鸽,但這并不包含解析過程中瀏覽器加載外部資源,比如圖片贯被、腳本眼五、iframe等的一些過程。說白了刃榨,上面的4步僅僅是html結(jié)構(gòu)的渲染過程。而外部資源的加載在html結(jié)構(gòu)的渲染過程中是貫徹始終的双仍,即便繪制DOM節(jié)點(diǎn)已經(jīng)完成枢希,而外部資源仍然可能正在加載或者尚未加載。

8.4朱沃、Webkit主要渲染流程

Firefox瀏覽器Gecko渲染流程跟Webkit內(nèi)核渲染類似苞轿,大同小異茅诱,WebKit 和 Gecko 使用的術(shù)語(yǔ)略有不同,但整體流程是基本相同的搬卒。這里以Webkit內(nèi)核作為例子來(lái)說明瀏覽器渲染的主要流程瑟俭。

瀏覽器的渲染原理并非三言兩語(yǔ),幾個(gè)圖就能說明白的契邀,上圖說的只是介紹一個(gè)大環(huán)節(jié)的過程和步驟摆寄,這里拋磚引玉象征性說個(gè)大概,更多關(guān)于瀏覽器內(nèi)部工作原理的文章坯门,請(qǐng)閱讀:瀏覽器的工作原理:新式網(wǎng)絡(luò)瀏覽器幕后揭秘

8.5微饥、domReady的實(shí)現(xiàn)策略

上面的各個(gè)代碼實(shí)例中,并沒有考慮domReady古戴,程序也能正常運(yùn)行欠橘,因?yàn)槲覀儼裫avascript代碼寫在了body元素最后的位置。因?yàn)闉g覽器是從上到下现恼,從左向右渲染元素的肃续,這樣實(shí)例中的js代碼一定在domReady之后去執(zhí)行的。那為什么還要用domReady呢叉袍?事實(shí)上始锚,我們?cè)诰帉懘笮晚?xiàng)目的時(shí)候,js文件往往非常多畦韭,而且之間會(huì)相互調(diào)用疼蛾,大多數(shù)都是外部引用的,不把js代碼直接寫在頁(yè)面上艺配。這樣的話察郁,如果有個(gè)domReady這個(gè)方法,我們想用它就調(diào)用转唉,不管邏輯代碼寫在哪里皮钠,都是等到domReady之后去執(zhí)行的。

window.onload方法赠法,表示當(dāng)頁(yè)面所有的元素都加載完畢麦轰,并且所有要請(qǐng)求的資源也加載完畢才觸發(fā)執(zhí)行function這個(gè)匿名函數(shù)里邊的具體內(nèi)容。這樣肯定保證了代碼在domReady之后執(zhí)行砖织。使用window.onload方法在文檔外部資源不多的情況下不會(huì)有什么問題款侵,但是當(dāng)頁(yè)面中有大量遠(yuǎn)程圖片或要請(qǐng)求的遠(yuǎn)程資源時(shí),我們需要讓js在點(diǎn)擊每張圖片時(shí)侧纯,進(jìn)行相應(yīng)的操作新锈,如果此時(shí)外部資源還沒有加載完畢,點(diǎn)擊圖片是不會(huì)有任何反應(yīng)的眶熬,大大降低了用戶體驗(yàn)妹笆。那既然window.onload方法不可行块请,又該怎么做呢?

你肯定想到了jquery中的$(document).ready(function(){})方法了,其實(shí)jquery中的domReady應(yīng)該和window.onload的實(shí)現(xiàn)原理是大同小異的拳缠。為了解決window.onload的短板墩新,w3c 新增了一個(gè) DOMContentLoaded 事件。

這里提到了DOMContentLoaded事件窟坐,這里由于篇幅有限海渊,就不多做介紹,這里面也有很多細(xì)節(jié)可以學(xué)習(xí)狸涌,有興趣的童鞋切省,可以看看我之前收藏的兩篇文章:

你不知道的 DOMContentLoaded
淺談DOMContentLoaded事件及其封裝方法

學(xué)習(xí)就是一個(gè)無(wú)底洞,因?yàn)樯畈豢蓽y(cè)帕胆,才讓人不斷探索朝捆。

參考jquery中domReady的實(shí)現(xiàn)原理,來(lái)看一下javascript中domReady的實(shí)現(xiàn)策略懒豹。

在頁(yè)面的DOM樹創(chuàng)建完成后(也就是HTML解析第一步完成)即觸發(fā)芙盘,而無(wú)需等待其他資源的加載。即domReady實(shí)現(xiàn)策略:

1. 支持DOMContentLoaded事件的脸秽,就使用DOMContentLoaded事件儒老。
2. 不支持的就用來(lái)自Diego Perini發(fā)現(xiàn)的著名Hack兼容。兼容原理大概就是通過IE中的document记餐,
documentElement.doScroll('left')來(lái)判斷DOM樹是否創(chuàng)建完畢驮樊。

JavaScript實(shí)現(xiàn)domReady,【domReady.js】

function myReady(fn){  
    //對(duì)于現(xiàn)代瀏覽器片酝,對(duì)DOMContentLoaded事件的處理采用標(biāo)準(zhǔn)的事件綁定方式  
    if ( document.addEventListener ) {  
        document.addEventListener("DOMContentLoaded", fn, false);  
    } else {  
        IEContentLoaded(fn);  
    }  
    //IE模擬DOMContentLoaded  
    function IEContentLoaded (fn) {  
        var d = window.document;  
        var done = false;  
  
        //只執(zhí)行一次用戶的回調(diào)函數(shù)init()  
        var init = function () {  
            if (!done) {  
                done = true;  
                fn();  
            }  
        };  
        (function () {  
            try {  
                // DOM樹未創(chuàng)建完之前調(diào)用doScroll會(huì)拋出錯(cuò)誤  
                d.documentElement.doScroll('left');  
            } catch (e) {  
                //延遲再試一次~  
                setTimeout(arguments.callee, 50);  
                return;  
            }  
            // 沒有錯(cuò)誤就表示DOM樹創(chuàng)建完畢囚衔,然后立馬執(zhí)行用戶回調(diào)  
            init();  
        })();  
        //監(jiān)聽document的加載狀態(tài)  
        d.onreadystatechange = function() {  
            // 如果用戶是在domReady之后綁定的函數(shù),就立馬執(zhí)行  
            if (d.readyState == 'complete') {  
                d.onreadystatechange = null;  
                init();  
            }  
        }  
    }  
}  

在頁(yè)面中引入donReady.js文件雕沿,引用myReady(回調(diào)函數(shù))方法即可练湿。

感興趣的童鞋可以看看各個(gè)主流框架domReady的實(shí)現(xiàn):點(diǎn)擊我查看

8.6、一個(gè)小栗子看二者差異性

下面通過一個(gè)案例审轮,來(lái)比較domReady與window.onload實(shí)現(xiàn)的不同肥哎,很明顯,onload事件是要在所有請(qǐng)求都完成之后才執(zhí)行疾渣,而domReady利用hack技術(shù)篡诽,在加載完dom樹之后就能執(zhí)行,所以domReady比onload執(zhí)行時(shí)間更早榴捡,建議采用domReady杈女。

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="utf-8">
    <title>domReady與window.onload</title>
    <script src="domReady.js"></script>
</head>

<body>
    <div id="showMsg"></div>
    <div>
        ![](http://upload-images.jianshu.io/upload_images/2448752-cd8e42a611905e4d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
        ![](http://upload-images.jianshu.io/upload_images/2448752-2403772c32d2f2ca.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
        ![](http://upload-images.jianshu.io/upload_images/2448752-8f4bf5f748c1cf33.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
        ![](http://upload-images.jianshu.io/upload_images/2448752-442cde34eba975a9.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
        ![](http://upload-images.jianshu.io/upload_images/2448752-0c0190100ab38644.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
        ![](http://upload-images.jianshu.io/upload_images/2448752-c9da8c538baa7870.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    </div>
    <script>
    var d = document;
    var msgBox = d.getElementById("showMsg");
    var imgs = d.getElementsByTagName("img");
    var time1 = null,
        time2 = null;
    myReady(function() {
        msgBox.innerHTML += "dom已加載!<br>";
        time1 = new Date().getTime();
        msgBox.innerHTML += "時(shí)間戳:" + time1 + "<br>";
    });
    window.onload = function() {
        msgBox.innerHTML += "onload已加載!<br>";
        time2 = new Date().getTime();
        msgBox.innerHTML += "時(shí)間戳:" + time2 + "<br>";
        msgBox.innerHTML += "domReady比onload快:" + (time2 - time1) + "ms<br>";
    };
    </script>
</body>

</html>

執(zhí)行結(jié)果對(duì)比碧信,發(fā)現(xiàn)DomReady比onload快樂2秒多。

9街夭、元素節(jié)點(diǎn)的判斷

為什么要判斷元素的節(jié)點(diǎn)砰碴?

因?yàn)橐袛嘣毓?jié)點(diǎn)類型,因?yàn)閷傩缘囊幌盗胁僮髋c元素的節(jié)點(diǎn)類型息息相關(guān)板丽,如果我們不區(qū)分它們呈枉,我們就不知道用元素的直接屬性操作(例如:ele.xxx=yyy)還是用一個(gè)方法操作(el.setAttribute(xxx,yyy))。

設(shè)計(jì)元素類型的判定埃碱,這里給出有4個(gè)方法:

(1).  isElement  :判定某個(gè)節(jié)點(diǎn)是否為元素節(jié)點(diǎn)
(2).  isHTML     :判定某個(gè)節(jié)點(diǎn)是否為html文檔的元素節(jié)點(diǎn)
(3).  isXML       : 判定某個(gè)節(jié)點(diǎn)是否為xml文檔的元素節(jié)點(diǎn)
(4).  contains   :用來(lái)判定兩個(gè)節(jié)點(diǎn)的包含關(guān)系

9.1猖辫、元素節(jié)點(diǎn)的判定:isElement

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>isElement</title>  
</head>  
<body>  
    <div id="test">aaa</div>  
    <!--這是一個(gè)注釋節(jié)點(diǎn)-->  
    <script>  
        var isElement = function (el){  
            return !!el && el.nodeType === 1;  
        }  
        var a = {            //隨意定義一個(gè)變量,設(shè)置nodeType為1  
           nodeType: 1  
        }  
        console.log(isElement(document.getElementById("test")));   
        //結(jié)果:  true  
        
        console.log(isElement(document.getElementById("test").nextSibling));
        //這里的nextSibling屬性查找下一個(gè)相鄰節(jié)點(diǎn)砚殿,即注釋節(jié)點(diǎn)  
        //結(jié)果:  false  
        
        console.log(isElement(a));  
        //結(jié)果:  true  
    </script>  
</body>  
</html>  

注意代碼中的!!用法:!!一般用來(lái)將后面的表達(dá)式轉(zhuǎn)換為布爾型的數(shù)據(jù)(boolean).

因?yàn)閖avascript是弱類型的語(yǔ)言(變量沒有固定的數(shù)據(jù)類型)所以有時(shí)需要強(qiáng)制轉(zhuǎn)換為相應(yīng)的類型,關(guān)于JavaScript的隱式轉(zhuǎn)換啃憎,可以看看之前我寫的一篇博客,這篇文章幾乎分析到了所有的轉(zhuǎn)換規(guī)則似炎,感興趣的童鞋可以點(diǎn)擊查閱辛萍,學(xué)習(xí)了解一下。

從++[[]][+[]]+[+[]]==10?深入淺出弱類型JS的隱式轉(zhuǎn)換

注意:上面的代碼定義了一個(gè)變量a羡藐,將它的nodeType的值設(shè)為1贩毕,由于元素節(jié)點(diǎn)的節(jié)點(diǎn)類型的數(shù)值常量為1,所以這里在打印的的時(shí)候仆嗦,會(huì)將a認(rèn)為是元素節(jié)點(diǎn)辉阶,所以打印true。這種結(jié)果明顯不是我們想要的瘩扼,即使這種情況很少出現(xiàn)谆甜。下面給出解決方案:

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>isElement</title>  
</head>  
<body>  
    <div id="test">aaa</div>  
    <!--這是一個(gè)注釋節(jié)點(diǎn)-->  
    <script>  
        var testDiv = document.createElement('div');  
        var isElement = function (obj) {  
            if (obj && obj.nodeType === 1) {//先過濾最簡(jiǎn)單的  
                if( window.Node && (obj instanceof Node )){ 
                //如果是IE9,則判定其是否Node的實(shí)例  
                    return true; //由于obj可能是來(lái)自另一個(gè)文檔對(duì)象,因此不能輕易返回false  
                }  
                try {//最后以這種效率非常差但肯定可行的方案進(jìn)行判定  
                    testDiv.appendChild(obj);  
                    testDiv.removeChild(obj);  
                } catch (e) {  
                    return false;  
                }  
                return true;  
            }  
            return false;  
        }  
        var a = {  
           nodeType: 1  
        }  
        console.log(isElement(document.getElementById("test")));  
        //結(jié)果:  true  
        console.log(isElement(document.getElementById("test").nextSibling));  
        //結(jié)果:  false  
        console.log(isElement(a));  
        //結(jié)果:  false  
    </script>  
</body>  
</html>  

這樣邢隧,在判斷a是否是元素節(jié)點(diǎn)時(shí)店印,結(jié)果就是false了。

更多關(guān)于元素節(jié)點(diǎn)的判斷請(qǐng)參考:How do you check if a JavaScript Object is a DOM Object?

9.2倒慧、HTML文檔元素節(jié)點(diǎn)的判定和XML文檔元素節(jié)點(diǎn)的判定:isHTML and isXML

我們可以簡(jiǎn)單的將所有的元素節(jié)點(diǎn)化為兩類:一類是HTML按摘,一類是XML。不過從嚴(yán)格意義上來(lái)說纫谅,HTML只是XML的一個(gè)子集炫贤,它擁有更多的特性,而XML在矢量繪圖的處理上又派生出了兩大類:SVG和VML付秕。那么按照這種方法兰珍,我們可以簡(jiǎn)單的認(rèn)為如果不是HTML,就是XML的元素節(jié)點(diǎn)了询吴。而HTML是比較容易識(shí)別的掠河,因?yàn)樗懈嗟奶匦粤猎1热缯f,XML是沒有className的唠摹,或者我們通過一個(gè)元素的ownerDocument得到它的文檔對(duì)象爆捞,XML是沒有document.getElementById()和document.getElementsByTagName()這些方法的.此外,最大的區(qū)別是HTML元素的nodeName總是大寫的勾拉,當(dāng)你使用createElement()方法創(chuàng)建HTML元素的時(shí)候煮甥,無(wú)論你傳入的字母是大寫還是小寫,最后得到的都是大寫藕赞。
接下來(lái)就看看各大類庫(kù)是怎么實(shí)現(xiàn)HTML和XML文檔的元素節(jié)點(diǎn)的判定的成肘。

9.2.1、Sizzle, jQuery自帶的選擇器引擎,判斷是否是XML文檔
<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>isXML</title>  
</head>  
<body>  
    <script>  
        //Sizzle, jQuery自帶的選擇器引擎  
        var isXML = function(elem) {  
            var documentElement = elem && (elem.ownerDocument || elem).documentElement;  
            return documentElement ? documentElement.nodeName !== "HTML" : false;  
        };  
        console.log(isXML(document.getElementById("test")));  
  
        //但這樣不嚴(yán)謹(jǐn)斧蜕,因?yàn)閄ML的根節(jié)點(diǎn)双霍,也可能是HTML標(biāo)簽,比如這樣創(chuàng)建一個(gè)XML文檔  
        try {  
            var doc = document.implementation.createDocument(null, 'HTML', null);  
            console.log(doc.documentElement);  
            console.log(isXML(doc));  
        } catch (e) {  
            console.log("不支持creatDocument方法");  
        }  
    </script>  
</body>  
</html>  

瀏覽器隨便找個(gè)HTML頁(yè)面驗(yàn)證一下:

9.2.1批销、mootools的slick選擇器引擎的源碼,判斷是否是XML文檔
<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>isXML</title>  
</head>  
<body>  
    <script>  
        //我們看看mootools的slick選擇器引擎的源碼:  
        var isXML = function(document) {  
            return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]')  
                    || (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');  
        };  
  
        //精簡(jiǎn)版  
        var isXML = window.HTMLDocument ? function(doc) {  
            return !(doc instanceof HTMLDocument);  
        } : function(doc) {  
            return "selectNodes" in doc;  
        }  
    </script>  
</body>  
</html>  

不過店煞,這些方法都只是規(guī)范,javascript對(duì)象是可以隨意添加的风钻,屬性法很容易被攻破顷蟀,最好是使用功能法。功能法的實(shí)現(xiàn)代碼如下:

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>isXML</title>  
</head>  
<body>  
    <script>  
        var isXML = function(doc) {  
            return doc.createElement("p").nodeName !== doc.createElement("P").nodeName;  
        }  
          
    </script>  
</body>  
</html>  

我們知道骡技,無(wú)論是HTML文檔鸣个,還是XML文檔都支持createELement()方法,我們判定創(chuàng)建的元素的nodeName是區(qū)分大小寫的還是不區(qū)分大小寫的布朦,我們就知道是XML還是HTML文檔囤萤,這個(gè)方法是目前給出的最嚴(yán)謹(jǐn)?shù)暮瘮?shù)了。

判斷是不是HTML文檔的方法如下:

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>isHTML</title>  
</head>  
<body>  
    <script>  
        var isHTML = function(doc) {  
            return doc.createElement("p").nodeName === doc.createElement("P").nodeName;  
        }  
        console.log(isHTML(document));  
    </script>  
</body>  
</html>  

有了以上判斷XML和HTML文檔的方法是趴,我們就可以實(shí)現(xiàn)一個(gè)元素節(jié)點(diǎn)屬于HTML還是XML文檔的方法了涛舍,實(shí)現(xiàn)代碼如下:

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>isHTMLElement</title>  
</head>  
<body>  
    <script>  
        var testDiv = document.createElement('div');  
        var isElement = function (obj) {  
            if (obj && obj.nodeType === 1) {//先過濾最簡(jiǎn)單的  
                if( window.Node && (obj instanceof Node )){ 
                //如果是IE9,則判定其是否Node的實(shí)例  
                    return true; //由于obj可能是來(lái)自另一個(gè)文檔對(duì)象,因此不能輕易返回false  
                }  
                try {//最后以這種效率非常差但肯定可行的方案進(jìn)行判定  
                    testDiv.appendChild(obj);  
                    testDiv.removeChild(obj);  
                } catch (e) {  
                    return false;  
                }  
                return true;  
            }  
            return false;  
        }  
        var isHTML = function(doc) {  
            return doc.createElement("p").nodeName === doc.createElement("P").nodeName;  
        }  
        var isHTMLElement = function(el){  
           if(isElement){  
              return isHTML(el.ownerDocument);  
           }  
           return false;  
        }  
        console.log(isHTMLElement(testDiv));  
    </script>  
</body>  
</html>  

9.3唆途、判斷節(jié)點(diǎn)的包含關(guān)系

DOM可以將任何HTML描繪成一個(gè)由多層節(jié)點(diǎn)構(gòu)成的結(jié)構(gòu)富雅。節(jié)點(diǎn)分為12種不同類型,每種類型分別表示文檔中不同的信息及標(biāo)記肛搬。每個(gè)節(jié)點(diǎn)都擁有各自的特點(diǎn)没佑、數(shù)據(jù)和方法,也與其他節(jié)點(diǎn)存在某種關(guān)系温赔。節(jié)點(diǎn)之間的關(guān)系構(gòu)成了層次蛤奢,而所有頁(yè)面標(biāo)記則表現(xiàn)為一個(gè)以特定節(jié)點(diǎn)為根節(jié)點(diǎn)的樹形結(jié)構(gòu)。DOM間的節(jié)點(diǎn)關(guān)系大致如下。



  
節(jié)點(diǎn)關(guān)系不僅僅指元素節(jié)點(diǎn)的關(guān)系啤贩,document文檔節(jié)點(diǎn)也包含在內(nèi)待秃。在最新的瀏覽器中,所有的節(jié)點(diǎn)都已經(jīng)裝備了contains()方法,
元素之間的包含關(guān)系痹屹,用自帶的contains方法锥余,只有兩個(gè)都是元素節(jié)點(diǎn),才能兼容各個(gè)瀏覽器痢掠,否則ie瀏覽器有的版本是不支持的,可以采用hack技術(shù)嘲恍,自己寫一個(gè)contains方法去兼容足画。
元素之間的包含關(guān)系:contains()方法.

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>contains</title>  
</head>  
<body>  
    <div id="p-node">  
        <div id="c-node">子節(jié)點(diǎn)內(nèi)容</div>  
    </div>  
    <script>  
        var pNode = document.getElementById("p-node");  
        var cNode = document.getElementById("c-node").childNodes[0];  
        alert(pNode.contains(cNode));    //true  
    </script>  
</body>  
</html>  

兼容各瀏覽器的contains()方法

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>contains</title>  
</head>  
<body>  
    <div id="p-node">  
        <div id="c-node">子節(jié)點(diǎn)內(nèi)容</div>  
    </div>  
    <script>  
        //兼容的contains方法  
        function fixContains(a, b) {  
            try {  
                while ((b = b.parentNode)){  
                    if (b === a){  
                        return true;  
                    }  
                }  
                return false;  
            } catch (e) {  
                return false;  
            }  
        }  
        var pNode = document.getElementById("p-node");  
        var cNode = document.getElementById("c-node").childNodes[0];  
        alert(fixContains(pNode, cNode));        //true  
        alert(fixContains(document, cNode));     //true  
    </script>  
</body>  
</html>  

10、DOM節(jié)點(diǎn)繼承層次與嵌套規(guī)則

10.1佃牛、DOM節(jié)點(diǎn)繼承層次

DOM節(jié)點(diǎn)是一個(gè)非常復(fù)雜的東西淹辞,對(duì)它的每一個(gè)屬性的訪問,不走運(yùn)的話俘侠,就可能會(huì)向上溯尋到N多個(gè)原型鏈象缀,因此DOM操作是個(gè)非常耗性能的操作。風(fēng)頭正盛的react為了解決這個(gè)問題爷速,提出了虛擬DOM的概念央星,合并和屏蔽了很多無(wú)效的DOM操作,效果非常驚人惫东。接下來(lái)看看DOM節(jié)點(diǎn)究竟是如何繼承的莉给。

10.1.1、創(chuàng)建一個(gè)元素節(jié)點(diǎn)(Element)的過程

使用document.createElement("p")創(chuàng)建p元素廉沮,其實(shí)document.createElement("p")是HTMLParagraphElement的一個(gè)實(shí)例颓遏,而HTMLParagraphElement的父類是HTMLElement,HTMLElement的父類是Element滞时,Element的父類是Node叁幢,Node的父類是EventTarget,EventTarget的父類是Function坪稽,F(xiàn)unction的父類是Object曼玩。

創(chuàng)建一個(gè)p元素一共溯尋了7層原型鏈:


下面我們來(lái)分析一下創(chuàng)建一個(gè)元素所繼承的屬性分別是啥。

1.document.createElement("p")

document.createElement("p")首先就是一個(gè)實(shí)例對(duì)象窒百,它是由構(gòu)造函數(shù)HTMLParagraphElement產(chǎn)生的演训,你可以這么看這個(gè)問題:

function HTMLParagraphElement() { 
    [native code] 
}

document.createElement("p")=new HTMLParagraphElement('p');

由以上繼承關(guān)系可以看出來(lái):

document.createElement("p").constructor===HTMLParagraphElement document.createElement("p").__proto__===HTMLParagraphElement.prototype

對(duì)實(shí)例對(duì)象,構(gòu)造函數(shù)贝咙,以及JavaScript原型鏈和繼承不太熟悉的童鞋样悟,該補(bǔ)習(xí)一下基礎(chǔ)看看了。

我們先來(lái)看看document.createElement("p")自身有哪些屬性,遍歷對(duì)象屬性方法一般有三種:

先來(lái)講一講遍歷對(duì)象屬性三種方法的差異性窟她,當(dāng)做補(bǔ)充復(fù)習(xí)陈症。
遍歷數(shù)組屬性目前我知道的有:for-in循環(huán)、Object.keys()Object.getOwnPropertyNames(),那么三種到底有啥區(qū)別呢?

for-in循環(huán):會(huì)遍歷對(duì)象自身的屬性,以及原型屬性,包括enumerable 為 false(不可枚舉屬性);
Object.keys():可以得到自身可枚舉的屬性,但得不到原型鏈上的屬性;
Object.getOwnPropertyNames():可以得到自身所有的屬性(包括不可枚舉),但得不到原型鏈上的屬性,Symbols屬性
也得不到.

Object.defineProperty顧名思義震糖,就是用來(lái)定義對(duì)象屬性的录肯,vue.js的雙向數(shù)據(jù)綁定主要在gettersetter函數(shù)里面插入一些處理方法,當(dāng)對(duì)象被讀寫的時(shí)候處理方法就會(huì)被執(zhí)行了吊说。 關(guān)于這些方法和屬性的更具體解釋论咏,可以看MDN上的解釋(戳我);

簡(jiǎn)單看一個(gè)小demo例子加深理解,對(duì)于Object.defineProperty屬性不太明白,可以看看上面介紹的文檔學(xué)習(xí)補(bǔ)充一下.

'use strict';
class A {
    constructor() {
        this.name = 'jawil';
    }
    getName() {}
}
class B extends A {
    constructor() {
            super();
            this.age = 22;
        }
        //getAge不可枚舉
    getAge() {}
        [Symbol('fullName')]() {

        }
}
B.prototype.get = function() {

}
var b = new B();

//設(shè)置b對(duì)象的info屬性的enumerable: false,讓其不能枚舉.
Object.defineProperty(b, 'info', {
    value: 7,
    writable: true,
    configurable: true,
    enumerable: false
});

//Object可以得到自身可枚舉的屬性,但得不到原型鏈上的屬性
console.log(Object.keys(b)); //[ 'name', 'age' ]


//Object可A以得到自身所有的屬性(包括不可枚舉),但得不到原型鏈上的屬性,Symbols屬性也得不到
console.log(Object.getOwnPropertyNames(b)); //[ 'name', 'age', 'info' ]

for (var attr in b) {
    console.log(attr);//name,age,get
}

//in會(huì)遍歷對(duì)象自身的屬性,以及原型屬性
console.log('getName' in b); //true

有了上面的知識(shí)作為擴(kuò)充颁井,我們就可以清晰明了的知道厅贪,創(chuàng)建元素P標(biāo)簽每一步都繼承了哪些屬性,繼承對(duì)象自身有哪些屬性雅宾,由于篇幅有限拨扶,大家可以自行子在瀏覽器測(cè)試侥涵,看看這些對(duì)象的一些屬性和方法梳庆,便于我們理解瞒爬。

例如我們想看:HTMLElement對(duì)象有哪些自身屬性,我們可以這么查看:

Object.getOwnPropertyNames(HTMLElement)

我們想看:HTMLElement的原型對(duì)象有哪些自身屬性蜀变,我們可以這么查看:

Object.getOwnPropertyNames(HTMLElement.prototype)

HTMLElement的原型對(duì)象有哪些自身屬性悄谐,根據(jù)原型鏈,我們也可以這么查看:

因?yàn)椋?code>document.createElement("p").__proto__.__proto__===HTMLElement.prototype

Object.getOwnPropertyNames(document.createElement("p").__proto__.__proto__)
10.1.2库北、創(chuàng)建一個(gè)文本節(jié)點(diǎn)(Text)的過程

使用document.createTextNode("xxx")創(chuàng)建文本節(jié)點(diǎn)尊沸,其實(shí)document.createTextNode("xxx")是Text的一個(gè)實(shí)例,而Text的父類是CharactorData贤惯,CharactorData的父類是Node洼专,Node的父類是EventTarget,EventTarget的父類是Function孵构,F(xiàn)unction的父類是Object屁商。

創(chuàng)建一個(gè)文本節(jié)點(diǎn)一共溯尋了6層原型鏈。

因此颈墅,所有節(jié)點(diǎn)的繼承層次都不簡(jiǎn)單蜡镶,但相比較而言,元素節(jié)點(diǎn)是更可怕的恤筛。從HTML1升級(jí)到HTML3.2官还,再升級(jí)到HTML4.1,再到HTML5毒坛,除了不斷地增加新類型望伦、新的嵌套規(guī)則以外林说,每個(gè)元素也不斷的添加新屬性。
下面看一個(gè)例子:創(chuàng)建一個(gè)p元素屯伞,打印它第一層原型的固有的屬性的名字腿箩,通過Object的getOwnPropertyNames()獲取當(dāng)前元素的一些屬性,這些屬性都是他的原始屬性劣摇,不包含用戶自定義的屬性珠移。

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <title>DOM inheritance hierarchy</title>  
</head>  
<body>  
    <script>  
console.log(Object.getOwnPropertyNames(document.createElement("p").__proto__));  
        //訪問p元素上一層原型控制臺(tái)打印:  ["align","constructor"]  
        console.log(
        Object.getOwnPropertyNames(document.createElement("p").__proto__.__proto__)
        );  
        /*訪問p元素上一層原型的再上一層原型,控制臺(tái)會(huì)打印很多屬性末融,感興趣的伙伴可以自己貼代碼到控制臺(tái)看
        一下钧惧,它要比訪*問第一層原型的屬性多得多。這也就是說勾习,每往上一層浓瞪,原型鏈就為它添加一些屬性。  
        */  
    </script>  
</body>  
</html>  
10.1.3语卤、空的div元素的自有屬性

下面看一個(gè)空的div元素,并且沒有插入到DOM里邊酪刀,看它有多少自有屬性(不包括原型鏈繼承來(lái)的屬性)

在新的HTML規(guī)范中粹舵,許多元素的固有屬性(比如value)都放到了原型鏈當(dāng)中,數(shù)量就更加龐大了骂倘。因此眼滤,未來(lái)的發(fā)展方向是盡量使用現(xiàn)成的框架來(lái)實(shí)現(xiàn),比如MVVM框架历涝,將所有的DOM操作都轉(zhuǎn)交給框架內(nèi)部做精細(xì)處理诅需,這些實(shí)現(xiàn)方案當(dāng)然就包括了虛擬DOM的技術(shù)了。但是在使用MVVM框架之前荧库,掌握底層知識(shí)是非常重要的堰塌,明白為什么這樣做,為什么不這樣做的目的分衫。這也是為什么要理解DOM節(jié)點(diǎn)繼承層次的目的场刑。

10.2、HTML嵌套規(guī)則

HTML存在許多種類型的標(biāo)簽蚪战,有的標(biāo)簽下面只允許特定的標(biāo)簽存在牵现,這就叫HTML嵌套規(guī)則。
不按HTML嵌套規(guī)則寫邀桑,瀏覽器就不會(huì)正確解析瞎疼,會(huì)將不符合嵌套規(guī)則的節(jié)點(diǎn)放到目標(biāo)節(jié)點(diǎn)的下面,或者變成純文本壁畸。
關(guān)于HTML嵌套規(guī)則贼急,一定要掌握塊狀元素和內(nèi)聯(lián)元素的區(qū)別茅茂。

塊狀元素:一般是其他元素的容器,可容納內(nèi)聯(lián)元素和其他塊狀元素竿裂,塊狀元素排斥其他元素與其位于同一行玉吁,寬度(width)高度(height)起作用。常見塊狀元素為div和p

內(nèi)聯(lián)元素:內(nèi)聯(lián)元素只能容納文本或者其他內(nèi)聯(lián)元素腻异,它允許其他內(nèi)聯(lián)元素與其位于同一行进副,但寬度(width)高度(height)不起作用。常見內(nèi)聯(lián)元素為a.

塊狀元素與內(nèi)聯(lián)元素嵌套規(guī)則:

(1).塊元素可以包含內(nèi)聯(lián)元素或某些塊元素悔常,但內(nèi)聯(lián)元素卻不能包含塊元素影斑,它只能包含其他的內(nèi)聯(lián)元素

例:
<div><h1></h1><p></p></div>
<a href="#"><span></span></a>

(2).塊級(jí)元素不能放在<p>里面

例:<p><ol><li></li></ol></p><p><div></div></p>

(3).有幾個(gè)特殊的塊級(jí)元素提倡只能包含內(nèi)聯(lián)元素,不能再包含塊級(jí)元素机打,這幾個(gè)特殊的標(biāo)簽是:

h1矫户、h2、 h3残邀、h4皆辽、 h5、 h6芥挣、 p 驱闷、dt

(4).li標(biāo)簽可以包含div標(biāo)簽

例:
<li><div></div></li>

(5).塊級(jí)元素與塊級(jí)元素并列,內(nèi)聯(lián)元素與內(nèi)聯(lián)元素并列

例:
<div><h2></h2><p></p></div>
<div><a href="#"></a><span></span></div>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末空免,一起剝皮案震驚了整個(gè)濱河市空另,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蹋砚,老刑警劉巖扼菠,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異坝咐,居然都是意外死亡循榆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門墨坚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)冯痢,“玉大人,你說我怎么就攤上這事框杜∑珠梗” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵咪辱,是天一觀的道長(zhǎng)振劳。 經(jīng)常有香客問我,道長(zhǎng)油狂,這世上最難降的妖魔是什么历恐? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任寸癌,我火速辦了婚禮,結(jié)果婚禮上弱贼,老公的妹妹穿的比我還像新娘蒸苇。我一直安慰自己,他們只是感情好吮旅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布溪烤。 她就那樣靜靜地躺著,像睡著了一般庇勃。 火紅的嫁衣襯著肌膚如雪檬嘀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天责嚷,我揣著相機(jī)與錄音鸳兽,去河邊找鬼。 笑死罕拂,一個(gè)胖子當(dāng)著我的面吹牛揍异,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播爆班,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼衷掷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蛋济?” 一聲冷哼從身側(cè)響起棍鳖,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤炮叶,失蹤者是張志新(化名)和其女友劉穎碗旅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镜悉,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祟辟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侣肄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旧困。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖稼锅,靈堂內(nèi)的尸體忽然破棺而出吼具,到底是詐尸還是另有隱情,我是刑警寧澤矩距,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布拗盒,位于F島的核電站,受9級(jí)特大地震影響锥债,放射性物質(zhì)發(fā)生泄漏陡蝇。R本人自食惡果不足惜痊臭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望登夫。 院中可真熱鬧广匙,春花似錦、人聲如沸恼策。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)戏蔑。三九已至蹋凝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間总棵,已是汗流浹背鳍寂。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留情龄,地道東北人迄汛。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像骤视,于是被迫代替她去往敵國(guó)和親鞍爱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容