之前通過深入學(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)有:
- 元素節(jié)點(diǎn):上圖中<html>、<body>派阱、<p>等都是元素節(jié)點(diǎn)诬留,即標(biāo)簽。
- 文本節(jié)點(diǎn):向用戶展示的內(nèi)容,如<li>...</li>中的JavaScript文兑、DOM盒刚、CSS等文本。
- 屬性節(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)類型地回。
例子中的:html
、heade
、meta
刻像、title
畅买、body
、div
细睡、ul
谷羞、li
、script
都屬于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
蹂季、charset
、id
疏日、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ù)綁定主要在getter
和setter
函數(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>