之前通過深入學(xué)習(xí)DOM的相關(guān)知識,看了慕課網(wǎng)DOM探索之基礎(chǔ)詳解篇這個(gè)視頻(在最近看第三遍的時(shí)候,準(zhǔn)備記錄一點(diǎn)東西,算是對自己學(xué)習(xí)的一點(diǎn)總結(jié)),對DOM的理解又具體了一步,因?yàn)镈OM本來就是一個(gè)抽象和概念性的東西,每深入一步了解,在腦中就會稍微具體一點(diǎn),通過這次的對DOM的系統(tǒng)學(xué)習(xí),對DOM有一個(gè)比較深刻的理解,明白了DOM在JavaScript這門語言中舉足輕重的地位,了解了DOm的發(fā)展歷史,也讓我明白了存在瀏覽器瀏覽器兼容性的歷史原因,對DOM的結(jié)構(gòu)有了進(jìn)一步的認(rèn)知,對DOM的一些API也更加熟悉,對比較抽象和概念性的DOM認(rèn)知稍微具體了一些。下面就是自己深入學(xué)習(xí)DOM這門課程整理的一些筆記,大部分來自學(xué)習(xí)中查閱的資料以及視頻中老師講的一些關(guān)鍵性知識點(diǎn),當(dāng)然也不可或缺的有自己的一些記錄和理解。
原文收錄在我的 GitHub博客 (https://github.com/jawil/blog) ,喜歡的可以關(guān)注最新動(dòng)態(tài),大家一起多交流學(xué)習(xí),共同進(jìn)步,以學(xué)習(xí)者的身份寫博客,記錄點(diǎn)滴。
文章稍長,本文只論述DOM基礎(chǔ)概念,不涉及DOM的一些事件原理機(jī)制,頁面元素的操作和常用API的講解以及兼容性事項(xiàng),所以概念性東西比較多,稍微有點(diǎn)抽象,其中有筆記來大部分來自老師的口述,還有一部分是查閱的文檔,最后有一部分是自己的記錄和理解。
通過document.createElement("p")創(chuàng)建一個(gè)p元素一共溯尋了7層原型鏈,你知道嗎?
學(xué)習(xí)視頻地址:DOM探索之基礎(chǔ)詳解篇,老師講的很好,有興趣的可以結(jié)合視頻學(xué)習(xí)一下,建議看完視頻再看筆記,加深印象,你會受益匪淺。
1、什么是DOM?
DOM,文檔對象模型(Document Object Model)。DOM是 W3C(萬維網(wǎng)聯(lián)盟)的標(biāo)準(zhǔn),DOM定義了訪問HTML和XML文檔的標(biāo)準(zhǔn)。在W3C的標(biāo)準(zhǔn)中,DOM是獨(dú)于平臺和語言的接口,它允許程序和腳本動(dòng)態(tài)地訪問和更新文檔的內(nèi)容、結(jié)構(gòu)和樣式。
W3C DOM由以下三部分組成:
- 核心DOM - 針對任何結(jié)構(gòu)化文檔的標(biāo)準(zhǔn)模型
- XML DOM - 針對 XML 文檔的標(biāo)準(zhǔn)模型
- HTML DOM - 針對 HTML 文檔的標(biāo)準(zhǔn)模型
DOM(文檔對象模型)是針對xml經(jīng)過擴(kuò)展用于html的應(yīng)用程序編程接口,我們又叫API。DOM把整個(gè)頁面映射為一個(gè)多層的節(jié)點(diǎn)結(jié)構(gòu),html或xml頁面中的每個(gè)組成部分都是某種類型的節(jié)點(diǎn),這些節(jié)點(diǎn)又包含著不同類型的數(shù)據(jù)。
2、DOM的地位
我們知道,一個(gè)網(wǎng)頁是由html來搭建結(jié)構(gòu)的,通過css來定義網(wǎng)頁的樣式,而JavaScript賦予了頁面的行為,通過它我們可以與頁面進(jìn)行交互,實(shí)現(xiàn)頁面的動(dòng)畫效果等等。那javascript究竟通過什么來實(shí)現(xiàn)的呢?通過ECMAScript這個(gè)標(biāo)準(zhǔn),我們可以編寫程序讓瀏覽器來解析,利用ECMAScript,我們可以通過BOM對象(即browser object model)來操作瀏覽器窗口、瀏覽器導(dǎo)航對象(navigator)、屏幕分辨率(screen)、瀏覽器歷史(history)、cookie等等。但這個(gè)通過BOM來實(shí)現(xiàn)的交互遠(yuǎn)遠(yuǎn)不夠。要實(shí)現(xiàn)頁面的動(dòng)態(tài)交互和效果,操作html才是核心。那如何操作html呢?對,就是DOM,簡單的說,DOM給我們提供了用程序來動(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ì)的初級階段的DOM,現(xiàn)在習(xí)慣上被稱為DOM0,即:第0級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)的簡稱。DHTML并不是一項(xiàng)新技術(shù),而是將HTML、CSS、JavaScript技術(shù)組合的一種描述。即:
- 利用HTML把網(wǎng)頁標(biāo)記為各種元素
- 利用CSS設(shè)置元素樣式及其顯示位置
- 利用JavaScript操控頁面元素和樣式
利用DHTML,看起來可以很容易的控制頁面元素,并實(shí)現(xiàn)一此原本很復(fù)雜的效果(如:通過改變元素位置實(shí)現(xiàn)動(dòng)畫)。但事實(shí)并非如此,因?yàn)闆]有規(guī)范和標(biāo)準(zhǔn),兩種瀏覽器對相同功能的實(shí)現(xiàn)確完全不一樣。為了保持程序的兼容性,程序員必須寫一些探查代碼以檢測JavaScript是運(yùn)行于哪種瀏覽器之下,并提供與之對應(yīng)的腳本。JavaScript陷入了前所未有的混亂,DHTML也因此在人們心中留下了很差的印象。
我們在閱讀DOM標(biāo)準(zhǔn)的時(shí)候,經(jīng)常會看到DOM0級這樣的字眼,實(shí)際上DOM0級這個(gè)標(biāo)準(zhǔn)是不存在的。所謂DOM0級只是DOM
歷史坐標(biāo)系中的一個(gè)參照點(diǎn)而已,具體地說DOM0級就是指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月完成了第一級 DOM,即:DOM1。W3C將DOM定義為一個(gè)與平臺和編程語言無關(guān)的接口,通過這個(gè)接口程序和腳本可以動(dòng)態(tài)的訪問和修改文檔的內(nèi)容、結(jié)構(gòu)和樣式。
DOM1級主要定義了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)簡化了對文檔中任意部分的訪問和操作。DOM HTML則在DOM核心的基礎(chǔ)上加以擴(kuò)展,添加了針對HTML的對象和方法,如:JavaScript中的Document對象.
3.4、DOM2
在DOM1的基礎(chǔ)上DOM2引入了更多的交互能力,也支持了更高級的XML特性。DOM2將DOM分為更多具有聯(lián)系的模塊。DOM2級在原來DOM的基礎(chǔ)上又?jǐn)U充了鼠標(biāo)、用戶界面事件、范圍、遍歷等細(xì)分模塊,而且通過對象接口增加了對CSS的支持。DOM1級中的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)(圖片來自百度百科):
3.5、DOM3
DOM3級:進(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)識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)成了層次,而所有頁面標(biāo)記則表現(xiàn)為一個(gè)以特定節(jié)點(diǎn)為根節(jié)點(diǎn)的樹形結(jié)構(gòu)。
先看一張w3school上面的一張圖:
先來看看下面代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>DOM</title>
</head>
<body>
<h2><a >javascript DOM</a></h2>
<p>對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文檔對象模型是從文檔中抽象出來的,DOM操作的對象也是文檔,因此我們有必要了解一下文檔的類型。文檔隨著歷史的發(fā)展演變?yōu)槎喾N類型,如下:
5.1、GML
GML(Generalized Markup Language, 通用標(biāo)記語言)是1960年代的一種IBM文檔格式化語言,用于描述文檔的組織結(jié)構(gòu)、各部件及其相互關(guān)系。GML在文檔具體格式方面,為文檔員提供了一些方便,他們不必再為IBM的打印機(jī)格式化語言SCRIPT要求的字體規(guī)范、行距以及頁面設(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)記語言)是1986年基于IBM的GML制定ISO標(biāo)準(zhǔn)(ISO 8879)。SGML是現(xiàn)時(shí)常用的超文本格式的最高層次標(biāo)準(zhǔn),是可以定義標(biāo)記語言的元語言,甚至可以定義不必采用"<>"的常規(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)記語言)是為“網(wǎng)頁創(chuàng)建和其它可在網(wǎng)頁瀏覽器中看到的信息”設(shè)計(jì)的一種標(biāo)記語言。HTML被用來結(jié)構(gòu)化信息——例如標(biāo)題、段落和列表等等,也可用來在一定程度上描述文檔的外觀和語義。1982年,蒂姆·伯納斯-李為使世界各地的物理學(xué)家能夠方便的進(jìn)行合作研究,創(chuàng)建了使用于其系統(tǒng)的HTML。之后HTML又不斷地?cái)U(kuò)充和發(fā)展,成為國際標(biāo)準(zhǔ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)記語言)是專家們使用SGML精簡制作,并依照HTML的發(fā)展經(jīng)驗(yàn),產(chǎn)生出一套使用上規(guī)則嚴(yán)謹(jǐn),但是簡單的描述數(shù)據(jù)語言。XML在1995年開始有雛形,在1998二月發(fā)布為W3C的標(biāo)準(zhǔn)(XML1.0)
5.5、XHTML
XHTML(eXtensible HyperText Markup Language, 可擴(kuò)展超文本標(biāo)記語言)的表現(xiàn)方式與超文本標(biāo)記語言(HTML)類似,不過語法上更加嚴(yán)格。從繼承關(guān)系上講,HTML是一種基于標(biāo)準(zhǔn)通用標(biāo)記語言(SGML)的應(yīng)用,是一種非常靈活的置標(biāo)語言,而XHTML則基于可擴(kuò)展標(biāo)記語言(XML),XML是SGML的一個(gè)子集。XHTML 1.0在2000年1月26日成為W3C的推薦標(biāo)準(zhǔn)。
6、DOM節(jié)點(diǎn)類型
DOM1級定義了一個(gè)Node接口,這個(gè)Node接口在javascript中是作為Node類型來實(shí)現(xiàn)的。除了IE以外,其他所有瀏覽器都可以訪問這個(gè)類型。每個(gè)節(jié)點(diǎn)都有一個(gè)nodeType屬性,用于表明節(jié)點(diǎn)的類型。節(jié)點(diǎn)類型通過定義數(shù)值常量和字符常量兩種方式來表示,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>
以下引用均來自老師說的話,感覺每句話都很重要,所以就寫下來了。
(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)來表示的。
例子中的: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)在根元素之外,所以我們在構(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對象。比如聲明文檔類型時(shí)<!doctype html>就是文檔類型節(jié)點(diǎn)。
例子中的:<!DOCTYPE html>
就屬于DocumentType(文檔類型節(jié)點(diǎn));
(7)DocumentFragment(文檔片段節(jié)點(diǎn)):
是輕量級的或最小的Document對象,它表示文檔的一部分或者是一段,不屬于文檔樹。不過它有一種特殊的行為,該行為使得它非常有用。比如:當(dā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屬性來獲得節(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對象,其他瀏覽器數(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)常會犯這樣的錯(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í)候多多少少會遇到這種困惑和錯(cuò)誤,其實(shí)出現(xiàn)這種問題的原因就是我們沒有區(qū)分HTML標(biāo)簽和DOM節(jié)點(diǎn)的區(qū)別的緣故了,由這個(gè)問題就引出下面要說的domReady和瀏覽器渲染解析原理了。
8.1、什么是domReady?
html是一種標(biāo)記語言,它告訴我們這個(gè)頁面有什么內(nèi)容,但行為交互是需要通過DOM操作來實(shí)現(xiàn)的。我們不要以為有兩個(gè)尖括號就以為它是一個(gè)DOM了,html標(biāo)簽要通過瀏覽器解析才會變成DOM節(jié)點(diǎn),當(dāng)我們向地址欄傳入一個(gè)url的時(shí)候,我們開始加載頁面,就能看到內(nèi)容,在這期間就有一個(gè)DOM節(jié)點(diǎn)構(gòu)建的過程。節(jié)點(diǎn)是以樹的形式組織的,當(dāng)頁面上所有的html都轉(zhuǎn)換為節(jié)點(diǎn)以后,就叫做DOM樹構(gòu)建完畢,簡稱為domReady。
8.2、那么瀏覽器是如何將html標(biāo)簽解析變成DOM節(jié)點(diǎn)的呢?
實(shí)際上瀏覽器是通過渲染引擎來實(shí)現(xiàn)的。渲染引擎的職責(zé)就是把請求的內(nèi)容顯示到瀏覽器屏幕上。默認(rèn)情況下渲染引擎可以顯示html、xml文檔及圖片。通過插件(瀏覽器擴(kuò)展)它可以顯示其他類型的文檔,比如我們安裝pdf viewer插件,我們就可以顯示pdf文檔。這里專注渲染引擎的主要用途,即是將css格式化的html和圖片在瀏覽器上進(jìn)行顯示。
8.3、瀏覽器渲染引擎的基本渲染流程
瀏覽器渲染要做的事就是把CSS,HTML,圖片等靜態(tài)資源展示到用戶眼前。
渲染引擎首先通過網(wǎng)絡(luò)獲得所請求文檔的內(nèi)容,通常以8k分塊的方法來完成:
上圖就是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ù)語略有不同,但整體流程是基本相同的。這里以Webkit內(nèi)核作為例子來說明瀏覽器渲染的主要流程。
瀏覽器的渲染原理并非三言兩語,幾個(gè)圖就能說明白的,上圖說的只是介紹一個(gè)大環(huán)節(jié)的過程和步驟,這里拋磚引玉象征性說個(gè)大概,更多關(guān)于瀏覽器內(nèi)部工作原理的文章,請閱讀:瀏覽器的工作原理:新式網(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í)上,我們在編寫大型項(xiàng)目的時(shí)候,js文件往往非常多,而且之間會相互調(diào)用,大多數(shù)都是外部引用的,不把js代碼直接寫在頁面上。這樣的話,如果有個(gè)domReady這個(gè)方法,我們想用它就調(diào)用,不管邏輯代碼寫在哪里,都是等到domReady之后去執(zhí)行的。
window.onload方法,表示當(dāng)頁面所有的元素都加載完畢,并且所有要請求的資源也加載完畢才觸發(fā)執(zhí)行function這個(gè)匿名函數(shù)里邊的具體內(nèi)容。這樣肯定保證了代碼在domReady之后執(zhí)行。使用window.onload方法在文檔外部資源不多的情況下不會有什么問題,但是當(dāng)頁面中有大量遠(yuǎn)程圖片或要請求的遠(yuǎn)程資源時(shí),我們需要讓js在點(diǎn)擊每張圖片時(shí),進(jìn)行相應(yīng)的操作,如果此時(shí)外部資源還沒有加載完畢,點(diǎn)擊圖片是不會有任何反應(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è)無底洞,因?yàn)樯畈豢蓽y,才讓人不斷探索。
參考jquery中domReady的實(shí)現(xiàn)原理,來看一下javascript中domReady的實(shí)現(xiàn)策略。
在頁面的DOM樹創(chuàng)建完成后(也就是HTML解析第一步完成)即觸發(fā),而無需等待其他資源的加載。即domReady實(shí)現(xiàn)策略:
1. 支持DOMContentLoaded事件的,就使用DOMContentLoaded事件。
2. 不支持的就用來自Diego Perini發(fā)現(xiàn)的著名Hack兼容。兼容原理大概就是通過IE中的document,
documentElement.doScroll('left')來判斷DOM樹是否創(chuàng)建完畢。
JavaScript實(shí)現(xiàn)domReady,【domReady.js】
function myReady(fn){
//對于現(xiàn)代瀏覽器,對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會拋出錯(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();
}
}
}
}
在頁面中引入donReady.js文件,引用myReady(回調(diào)函數(shù))方法即可。
感興趣的童鞋可以看看各個(gè)主流框架domReady的實(shí)現(xiàn):點(diǎn)擊我查看
8.6、一個(gè)小栗子看二者差異性
下面通過一個(gè)案例,來比較domReady與window.onload實(shí)現(xiàn)的不同,很明顯,onload事件是要在所有請求都完成之后才執(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>






</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é)果對比,發(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 :用來判定兩個(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>
注意代碼中的!!用法:!!一般用來將后面的表達(dá)式轉(zhuǎn)換為布爾型的數(shù)據(jù)(boolean).
因?yàn)閖avascript是弱類型的語言(變量沒有固定的數(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í)候,會將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) {//先過濾最簡單的
if( window.Node && (obj instanceof Node )){
//如果是IE9,則判定其是否Node的實(shí)例
return true; //由于obj可能是來自另一個(gè)文檔對象,因此不能輕易返回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)的判斷請參考: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é)點(diǎn)化為兩類:一類是HTML,一類是XML。不過從嚴(yán)格意義上來說,HTML只是XML的一個(gè)子集,它擁有更多的特性,而XML在矢量繪圖的處理上又派生出了兩大類:SVG和VML。那么按照這種方法,我們可以簡單的認(rèn)為如果不是HTML,就是XML的元素節(jié)點(diǎn)了。而HTML是比較容易識別的,因?yàn)樗懈嗟奶匦?。比如說,XML是沒有className的,或者我們通過一個(gè)元素的ownerDocument得到它的文檔對象,XML是沒有document.getElementById()和document.getElementsByTagName()這些方法的.此外,最大的區(qū)別是HTML元素的nodeName總是大寫的,當(dāng)你使用createElement()方法創(chuàng)建HTML元素的時(shí)候,無論你傳入的字母是大寫還是小寫,最后得到的都是大寫。
接下來就看看各大類庫是怎么實(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à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');
};
//精簡版
var isXML = window.HTMLDocument ? function(doc) {
return !(doc instanceof HTMLDocument);
} : function(doc) {
return "selectNodes" in doc;
}
</script>
</body>
</html>
不過,這些方法都只是規(guī)范,javascript對象是可以隨意添加的,屬性法很容易被攻破,最好是使用功能法。功能法的實(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>
我們知道,無論是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) {//先過濾最簡單的
if( window.Node && (obj instanceof Node )){
//如果是IE9,則判定其是否Node的實(shí)例
return true; //由于obj可能是來自另一個(gè)文檔對象,因此不能輕易返回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)成了層次,而所有頁面標(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ù)雜的東西,對它的每一個(gè)屬性的訪問,不走運(yùn)的話,就可能會向上溯尋到N多個(gè)原型鏈,因此DOM操作是個(gè)非常耗性能的操作。風(fēng)頭正盛的react為了解決這個(gè)問題,提出了虛擬DOM的概念,合并和屏蔽了很多無效的DOM操作,效果非常驚人。接下來看看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層原型鏈:
下面我們來分析一下創(chuàng)建一個(gè)元素所繼承的屬性分別是啥。
1.document.createElement("p")
document.createElement("p")首先就是一個(gè)實(shí)例對象,它是由構(gòu)造函數(shù)HTMLParagraphElement產(chǎn)生的,你可以這么看這個(gè)問題:
function HTMLParagraphElement() {
[native code]
}
document.createElement("p")=new HTMLParagraphElement('p');
由以上繼承關(guān)系可以看出來:
document.createElement("p").constructor===HTMLParagraphElement document.createElement("p").__proto__===HTMLParagraphElement.prototype
對實(shí)例對象,構(gòu)造函數(shù),以及JavaScript原型鏈和繼承不太熟悉的童鞋,該補(bǔ)習(xí)一下基礎(chǔ)看看了。
我們先來看看document.createElement("p")自身有哪些屬性,遍歷對象屬性方法一般有三種:
先來講一講遍歷對象屬性三種方法的差異性,當(dāng)做補(bǔ)充復(fù)習(xí)。
遍歷數(shù)組屬性目前我知道的有:for-in
循環(huán)、Object.keys()
和Object.getOwnPropertyNames()
,那么三種到底有啥區(qū)別呢?
for-in循環(huán):會遍歷對象自身的屬性,以及原型屬性,包括enumerable 為 false(不可枚舉屬性);
Object.keys():可以得到自身可枚舉的屬性,但得不到原型鏈上的屬性;
Object.getOwnPropertyNames():可以得到自身所有的屬性(包括不可枚舉),但得不到原型鏈上的屬性,Symbols屬性
也得不到.
Object.defineProperty
顧名思義,就是用來定義對象屬性的,vue.js
的雙向數(shù)據(jù)綁定主要在getter
和setter
函數(shù)里面插入一些處理方法,當(dāng)對象被讀寫的時(shí)候處理方法就會被執(zhí)行了。 關(guān)于這些方法和屬性的更具體解釋,可以看MDN上的解釋(戳我);
簡單看一個(gè)小demo例子加深理解,對于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對象的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會遍歷對象自身的屬性,以及原型屬性
console.log('getName' in b); //true
有了上面的知識作為擴(kuò)充,我們就可以清晰明了的知道,創(chuàng)建元素P標(biāo)簽每一步都繼承了哪些屬性,繼承對象自身有哪些屬性,由于篇幅有限,大家可以自行子在瀏覽器測試,看看這些對象的一些屬性和方法,便于我們理解。
例如我們想看:HTMLElement對象有哪些自身屬性,我們可以這么查看:
Object.getOwnPropertyNames(HTMLElement)
我們想看:HTMLElement的原型對象有哪些自身屬性,我們可以這么查看:
Object.getOwnPropertyNames(HTMLElement.prototype)
HTMLElement的原型對象有哪些自身屬性,根據(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é)點(diǎn)是更可怕的。從HTML1升級到HTML3.2,再升級到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元素上一層原型控制臺打?。? ["align","constructor"]
console.log(
Object.getOwnPropertyNames(document.createElement("p").__proto__.__proto__)
);
/*訪問p元素上一層原型的再上一層原型,控制臺會打印很多屬性,感興趣的伙伴可以自己貼代碼到控制臺看
一下,它要比訪*問第一層原型的屬性多得多。這也就是說,每往上一層,原型鏈就為它添加一些屬性。
*/
</script>
</body>
</html>
10.1.3、空的div元素的自有屬性
下面看一個(gè)空的div元素,并且沒有插入到DOM里邊,看它有多少自有屬性(不包括原型鏈繼承來的屬性)
在新的HTML規(guī)范中,許多元素的固有屬性(比如value)都放到了原型鏈當(dāng)中,數(shù)量就更加龐大了。因此,未來的發(fā)展方向是盡量使用現(xiàn)成的框架來實(shí)現(xiàn),比如MVVM框架,將所有的DOM操作都轉(zhuǎn)交給框架內(nèi)部做精細(xì)處理,這些實(shí)現(xiàn)方案當(dāng)然就包括了虛擬DOM的技術(shù)了。但是在使用MVVM框架之前,掌握底層知識是非常重要的,明白為什么這樣做,為什么不這樣做的目的。這也是為什么要理解DOM節(jié)點(diǎn)繼承層次的目的。
10.2、HTML嵌套規(guī)則
HTML存在許多種類型的標(biāo)簽,有的標(biāo)簽下面只允許特定的標(biāo)簽存在,這就叫HTML嵌套規(guī)則。
不按HTML嵌套規(guī)則寫,瀏覽器就不會正確解析,會將不符合嵌套規(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).塊級元素不能放在<p>里面
例:<p><ol><li></li></ol></p><p><div></div></p>
(3).有幾個(gè)特殊的塊級元素提倡只能包含內(nèi)聯(lián)元素,不能再包含塊級元素,這幾個(gè)特殊的標(biāo)簽是:
h1、h2、 h3、h4、 h5、 h6、 p 、dt
(4).li標(biāo)簽可以包含div標(biāo)簽
例:
<li><div></div></li>
(5).塊級元素與塊級元素并列,內(nèi)聯(lián)元素與內(nèi)聯(lián)元素并列
例:
<div><h2></h2><p></p></div>
<div><a href="#"></a><span></span></div>