以前經過深刻學習DOM的相關知識,看了慕課網DOM探索之基礎詳解篇這個視頻(在最近看第三遍的時候,準備記錄一點東西,算是對本身學習的一點總結),對DOM的理解又具體了一步,由於DOM原本就是一個抽象和概念性的東西,每深刻一步瞭解,在腦中就會稍微具體一點,經過此次的對DOM的系統學習,對DOM有一個比較深入的理解,明白了DOM在JavaScript這門語言中舉足輕重的地位,瞭解了DOm的發展歷史,也讓我明白了存在瀏覽器瀏覽器兼容性的歷史緣由,對DOM的結構有了進一步的認知,對DOM的一些API也更加熟悉,對比較抽象和概念性的DOM認知稍微具體了一些。下面就是本身深刻學習DOM這門課程整理的一些筆記,大部分來自學習中查閱的資料以及視頻中老師講的一些關鍵性知識點,固然也不可或缺的有本身的一些記錄和理解。javascript
原文收錄在個人 GitHub博客 (https://github.com/jawil/blog) ,喜歡的能夠關注最新動態,你們一塊兒多交流學習,共同進步,以學習者的身份寫博客,記錄點滴。css
文章稍長,本文只論述DOM基礎概念,不涉及DOM的一些事件原理機制,頁面元素的操做和經常使用API的講解以及兼容性事項,因此概念性東西比較多,稍微有點抽象,其中有筆記來大部分來自老師的口述,還有一部分是查閱的文檔,最後有一部分是本身的記錄和理解。html
經過document.createElement("p")建立一個p元素一共溯尋了7層原型鏈,你知道嗎?
學習視頻地址:DOM探索之基礎詳解篇,老師講的很好,有興趣的能夠結合視頻學習一下,建議看完視頻再看筆記,加深印象,你會受益不淺。
vue
DOM,文檔對象模型(Document Object Model)。DOM是 W3C(萬維網聯盟)的標準,DOM定義了訪問HTML和XML文檔的標準。在W3C的標準中,DOM是獨於平臺和語言的接口,它容許程序和腳本動態地訪問和更新文檔的內容、結構和樣式。html5
W3C DOM由如下三部分組成:java
核心DOM - 針對任何結構化文檔的標準模型node
XML DOM - 針對 XML 文檔的標準模型react
HTML DOM - 針對 HTML 文檔的標準模型jquery
DOM(文檔對象模型)是針對xml通過擴展用於html的應用程序編程接口,咱們又叫API。DOM把整個頁面映射爲一個多層的節點結構,html或xml頁面中的每一個組成部分都是某種類型的節點,這些節點又包含着不一樣類型的數據。git
<img width="50%" src="http://ww1.sinaimg.cn/large/a...
咱們知道,一個網頁是由html來搭建結構的,經過css來定義網頁的樣式,而JavaScript賦予了頁面的行爲,經過它咱們能夠與頁面進行交互,實現頁面的動畫效果等等。那javascript究竟經過什麼來實現的呢?經過ECMAScript這個標準,咱們能夠編寫程序讓瀏覽器來解析,利用ECMAScript,咱們能夠經過BOM對象(即browser object model)來操做瀏覽器窗口、瀏覽器導航對象(navigator)、屏幕分辨率(screen)、瀏覽器歷史(history)、cookie等等。但這個經過BOM來實現的交互遠遠不夠。要實現頁面的動態交互和效果,操做html纔是核心。那如何操做html呢?對,就是DOM,簡單的說,DOM給咱們提供了用程序來動態控制html的接口,也就是早期的DHTMl的概念。所以,DOM處在javascript賦予html具有動態交互和效果的能力的核心地位上。
<img width="50%" src="http://ww1.sinaimg.cn/large/a...
JavaScript在早期版本中提供了查詢和操做Web文檔的內容API(如:圖像和表單),在JavaScript中定義了定義了'images'、'forms'等,所以咱們能夠像下這樣訪問第一張圖片或名爲「user」的表單:
document.images[0]document.forms['user']
這其實是未造成標準的試驗性質的初級階段的DOM,如今習慣上被稱爲DOM0,即:第0級DOM。因爲DOM0在W3C進行標準備化以前出現,還處於未造成標準的初期階段,這時Netscape和Microsoft各自推出本身的第四代瀏覽器,自此DOM遍開始出各類問題。
Netscape Navigator 4和IE4分別發佈於1997年的6月和10月,這兩種瀏覽器都大幅擴展了DOM,使JavaScript的功能大大增長,而此時也開始出現一個新名詞:DHTML。
DHTML是Dynamic HTML(動態HTML)的簡稱。DHTML並非一項新技術,而是將HTML、CSS、JavaScript技術組合的一種描述。即:
利用HTML把網頁標記爲各類元素
利用CSS設置元素樣式及其顯示位置
利用JavaScript操控頁面元素和樣式
利用DHTML,看起來能夠很容易的控制頁面元素,並實現一此本來很複雜的效果(如:經過改變元素位置實現動畫)。但事實並不是如此,由於沒有規範和標準,兩種瀏覽器對相同功能的實現確徹底不同。爲了保持程序的兼容性,程序員必須寫一些探查代碼以檢測JavaScript是運行於哪一種瀏覽器之下,並提供與之對應的腳本。JavaScript陷入了史無前例的混亂,DHTML也所以在人們心中留下了不好的印象。
咱們在閱讀DOM標準的時候,常常會看到DOM0級這樣的字眼,實際上DOM0級這個標準是不存在的。所謂DOM0級只是DOM 歷史座標系中的一個參照點而已,具體地說DOM0級就是指IE4.0和Netscape navigator4.0最初支持的那個DHTML。
在瀏覽器廠商進行瀏覽器大站的同時,W3C結合你們的優勢推出了一個標準化的DOM,並於1998年10月完成了第一級 DOM,即:DOM1。W3C將DOM定義爲一個與平臺和編程語言無關的接口,經過這個接口程序和腳本能夠動態的訪問和修改文檔的內容、結構和樣式。
DOM1級主要定義了HTML和XML文檔的底層結構。在DOM1中,DOM由兩個模塊組成:DOM Core(DOM核心)和DOM HTML。其中,DOM Core規定了基於XML的文檔結構標準,經過這個標準簡化了對文檔中任意部分的訪問和操做。DOM HTML則在DOM核心的基礎上加以擴展,添加了針對HTML的對象和方法,如:JavaScript中的Document對象.
在DOM1的基礎上DOM2引入了更多的交互能力,也支持了更高級的XML特性。DOM2將DOM分爲更多具備聯繫的模塊。DOM2級在原來DOM的基礎上又擴充了鼠標、用戶界面事件、範圍、遍歷等細分模塊,並且經過對象接口增長了對CSS的支持。DOM1級中的DOM核心模塊也通過擴展開始支持XML命名空間。在DOM2中引入了下列模塊,在模塊包含了衆多新類型和新接口:
DOM視圖(DOM Views):定義了跟蹤不一樣文檔視圖的接口
DOM事件(DOM Events):定義了事件和事件處理的接口
DOM樣式(DOM Style):定義了基於CSS爲元素應用樣式的接口
DOM遍歷和範圍(DOM Traversal and Range):定義了遍歷和操做文檔樹的接口
完整的DOM2標準(圖片來自百度百科):
DOM3級:進一步擴展了DOM,引入了以統一方式加載和保存文檔的方法,它在DOM Load And Save這個模塊中定義;同時新增了驗證文檔的方法,是在DOM Validation這個模塊中定義的。
DOM3進一步擴展了DOM,在DOM3中引入瞭如下模塊:
DOM加載和保存模塊(DOM Load and Save):引入了以統一方式加載和保存文檔的方法
DOM驗證模塊(DOM Validation):定義了驗證文檔的方法
DOM核心的擴展(DOM Style):支持XML 1.0規範,涉及XML Infoset、XPath和XML Base
DOM能夠將任何HTML描繪成一個由多層節點構成的結構。節點分爲12種不一樣類型,每種類型分別表示文檔中不一樣的信息及標記。每一個節點都擁有各自的特色、數據和方法,也與其餘節點存在某種關係。節點之間的關係構成了層次,而全部頁面標記則表現爲一個以特定節點爲根節點的樹形結構。
先看一張w3school上面的一張圖:
先來看看下面代碼:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>DOM</title> </head> <body> <h2><a href="http://www.baidu.com">javascript DOM</a></h2> <p>對HTML元素進行操做,可添加、改變或移除css樣式等</p> <ul> <li>Javascript</li> <li>DOM</li> <li>CSS</li> </ul> </body> </html>
將HTML代碼分解爲DOM節點層次圖:
HTML文檔能夠說由節點構成的集合,DOM節點有:
元素節點:上圖中<html>、<body>、<p>等都是元素節點,即標籤。
文本節點:向用戶展現的內容,如<li>...</li>中的JavaScript、DOM、CSS等文本。
屬性節點:元素屬性,如<a>標籤的連接屬性href="http://www.baidu.com"。
咱們說DOM文檔對象模型是從文檔中抽象出來的,DOM操做的對象也是文檔,所以咱們有必要了解一下文檔的類型。文檔隨着歷史的發展演變爲多種類型,以下:
GML(Generalized Markup Language, 通用標記語言)是1960年代的一種IBM文檔格式化語言,用於描述文檔的組織結構、各部件及其相互關係。GML在文檔具體格式方面,爲文檔員提供了一些方便,他們沒必要再爲IBM的打印機格式化語言SCRIPT要求的字體規範、行距以及頁面設計等浪費精力。這個IBM的GML包括1960年代的GML和1980年代的ISIL。
SGML(Standard Generalized Markup Language, 標準通用標記語言)是1986年基於IBM的GML制定ISO標準(ISO 8879)。SGML是現時經常使用的超文本格式的最高層次標準,是能夠定義標記語言的元語言,甚至能夠定義沒必要採用"<>"的常規方式。因爲SGML的複雜,於是難以普及。HTML和XML一樣衍生於SGML,XML能夠被認爲是SGML的一個子集,而HTML是SGML的一個應用。
HTML(HyperText Markup Language, 超文本標記語言)是爲「網頁建立和其它可在網頁瀏覽器中看到的信息」設計的一種標記語言。HTML被用來結構化信息——例如標題、段落和列表等等,也可用來在必定程度上描述文檔的外觀和語義。1982年,蒂姆·伯納斯-李爲使世界各地的物理學家可以方便的進行合做研究,建立了使用於其系統的HTML。以後HTML又不斷地擴充和發展,成爲國際標準,由萬維網聯盟(W3C)維護。第一個正式標準是1995年發佈的RFC 1866(HTML 2.0)。
XML(eXtensible Markup Language, 可擴展標記語言)是專家們使用SGML精簡製做,並依照HTML的發展經驗,產生出一套使用上規則嚴謹,可是簡單的描述數據語言。XML在1995年開始有雛形,在1998二月發佈爲W3C的標準(XML1.0)
XHTML(eXtensible HyperText Markup Language, 可擴展超文本標記語言)的表現方式與超文本標記語言(HTML)相似,不過語法上更加嚴格。從繼承關係上講,HTML是一種基於標準通用標記語言(SGML)的應用,是一種很是靈活的置標語言,而XHTML則基於可擴展標記語言(XML),XML是SGML的一個子集。XHTML 1.0在2000年1月26日成爲W3C的推薦標準。
DOM1級定義了一個Node接口,這個Node接口在javascript中是做爲Node類型來實現的。除了IE之外,其餘全部瀏覽器均可以訪問這個類型。每一個節點都有一個nodeType屬性,用於代表節點的類型。節點類型經過定義數值常量和字符常量兩種方式來表示,IE只支持數值常量。節點類型一共有12種,這裏介紹經常使用的7種類型。以下圖:
看下面這個例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>DocumentFragment文檔片斷節點</title> </head> <body> <!-- tip區域 --> <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>
如下引用均來自老師說的話,感受每句話都很重要,因此就寫下來了。
是組成文檔樹的重要部分,它表示了html、xml文檔中的元素。一般元素由於有子元素、文本節點或者二者的結合,元素節點是惟一可以擁有屬性的節點類型。
例子中的:html
、heade
、meta
、title
、body
、div
、ul
、li
、script
都屬於Element(元素節點);
表明了元素中的屬性,由於屬性其實是附屬於元素的,所以屬性節點不能被看作是元素的子節點。於是在DOM中屬性沒有被認爲是文檔樹的一部分。換句話說,屬性節點其實被看作是包含它的元素節點的一部分,它並不做爲單獨的一個節點在文檔樹中出現。
例子中的:lang
、charset
、id
、class
都屬於Attr(屬性節點);
是隻包含文本內容的節點,在xml中稱爲字符數據,它能夠由更多的信息組成,也能夠只包含空白。在文檔樹中元素的文本內容和屬性的文本內容都是由文本節點來表示的。
例子中的:DocumentFragment文檔片斷節點
、test1
、test2
、元素節點以後的空白區域
都屬於Text(文本節點);
表示註釋的內容
例子中的:<!-- tip區域 -->
都屬於Comment(註釋節點);
是文檔樹的根節點,它是文檔中其餘全部節點的父節點。要注意的是,文檔節點並非html、xml文檔的根元素,由於在xml文檔中,處理指令、註釋等內容能夠出如今根元素以外,因此咱們在構造DOM樹的時候,根元素並不適合做爲根節點,所以就有了文檔節點,而根元素是做爲文檔節點的子節點出現的。
例子中的:<!DOCTYPE html>
、html
做爲Document(文檔節點)的子節點出現;
每個Document都有一個DocumentType屬性,它的值或者是null,或者是DocumentType對象。好比聲明文檔類型時<!doctype html>就是文檔類型節點。
例子中的:<!DOCTYPE html>
就屬於DocumentType(文檔類型節點);
是輕量級的或最小的Document對象,它表示文檔的一部分或者是一段,不屬於文檔樹。不過它有一種特殊的行爲,該行爲使得它很是有用。好比:當請求把一個DocumentFragment節點插入到文檔的時候,插入的不是DocumentFragment自身,而是它的全部的子孫節點。這使得DocumentFragment成了有用的佔位符,暫時存放那些一次插入文檔的節點,同時它還有利於實現文檔的剪切、複製和粘貼等操做。
例子中的:var frag = document.createDocumentFragment();
就屬於DocumentFragment(文檔片斷節點);
經過DOM節點類型,咱們可知,能夠經過某個節點的nodeType屬性來得到節點的類型,節點的類型能夠是數值常量或者字符常量。示例代碼以下:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>nodeType</title> </head> <body> <div id="container">這是一個元素節點</div> <script> var divNode = document.getElementById('container'); /* IE中只支持數值常量,由於低版本IE瀏覽器沒有內置Node對象,其餘瀏覽器數值常量和字符常量都支持,所以可 以直接用數值常量判斷,這裏爲了比較兩種寫法,便都寫在了這裏 */ if (divNode.nodeType == Node.ELEMENT_NODE || divNode.nodeType === 1) { alert("Node is an element."); } </script> </body> </html>
先看示例代碼:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>nodeName,nodeValue</title> </head> <body> <!--nodeName,nodeValue實驗--> <div id="container">這是一個元素節點</div> <script> var divNode = document.getElementById('container'); console.log(divNode.nodeName + "/" + divNode.nodeValue); //結果: DIV/null var attrNode = divNode.attributes[0]; console.log(attrNode.nodeName + "/" + attrNode.nodeValue); //結果: id/container var textNode = divNode.childNodes[0]; console.log(textNode.nodeName + "/" + textNode.nodeValue); //結果: #text/這是一個元素節點 var commentNode = document.body.childNodes[1]; //表示取第二個註釋節點,由於body下面的第一個註釋節點爲空白符。 console.log(commentNode.nodeName + "/" +commentNode.nodeValue); //結果: #comment/nodeName,nodeValue實驗 console.log(document.doctype.nodeName + "/" + document.doctype.nodeValue); //結果: html/null var frag = document.createDocumentFragment(); console.log(frag.nodeName + "/" + frag.nodeValue); //結果: #document-fragment/null </script> </body> </html>
根據實驗,得出如下彙總表格:
還記得剛開始學習JavaScript時候,常常會犯這樣的錯誤:
<!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元素包含的內容</h1> </body> </html>
最後發現結果並非咱們想要的,文字並無變成紅色,我想最早入門學習JavaScript操做DOM時候多多少少會遇到這種困惑和錯誤,其實出現這種問題的緣由就是咱們沒有區分HTML標籤和DOM節點的區別的緣故了,由這個問題就引出下面要說的domReady和瀏覽器渲染解析原理了。
html是一種標記語言,它告訴咱們這個頁面有什麼內容,但行爲交互是須要經過DOM操做來實現的。咱們不要覺得有兩個尖括號就覺得它是一個DOM了,html標籤要經過瀏覽器解析纔會變成DOM節點,當咱們向地址欄傳入一個url的時候,咱們開始加載頁面,就能看到內容,在這期間就有一個DOM節點構建的過程。節點是以樹的形式組織的,當頁面上全部的html都轉換爲節點之後,就叫作DOM樹構建完畢,簡稱爲domReady。
實際上瀏覽器是經過渲染引擎來實現的。渲染引擎的職責就是把請求的內容顯示到瀏覽器屏幕上。默認狀況下渲染引擎能夠顯示html、xml文檔及圖片。經過插件(瀏覽器擴展)它能夠顯示其餘類型的文檔,好比咱們安裝pdf viewer插件,咱們就能夠顯示pdf文檔。這裏專一渲染引擎的主要用途,便是將css格式化的html和圖片在瀏覽器上進行顯示。
瀏覽器渲染要作的事就是把CSS,HTML,圖片等靜態資源展現到用戶眼前。
渲染引擎首先經過網絡得到所請求文檔的內容,一般以8k分塊的方法來完成:
上圖就是html渲染的基本過程,但這並不包含解析過程當中瀏覽器加載外部資源,好比圖片、腳本、iframe等的一些過程。說白了,上面的4步僅僅是html結構的渲染過程。而外部資源的加載在html結構的渲染過程當中是貫徹始終的,即使繪製DOM節點已經完成,而外部資源仍然可能正在加載或者還沒有加載。
Firefox瀏覽器Gecko渲染流程跟Webkit內核渲染相似,大同小異,WebKit 和 Gecko 使用的術語略有不一樣,但總體流程是基本相同的。這裏以Webkit內核做爲例子來講明瀏覽器渲染的主要流程。
瀏覽器的渲染原理並不是三言兩語,幾個圖就能說明白的,上圖說的只是介紹一個大環節的過程和步驟,這裏拋磚引玉象徵性說個大概,更多關於瀏覽器內部工做原理的文章,請閱讀:瀏覽器的工做原理:新式網絡瀏覽器幕後揭祕
上面的各個代碼實例中,並無考慮domReady,程序也能正常運行,由於咱們把javascript代碼寫在了body元素最後的位置。由於瀏覽器是從上到下,從左向右渲染元素的,這樣實例中的js代碼必定在domReady以後去執行的。那爲何還要用domReady呢?事實上,咱們在編寫大型項目的時候,js文件每每很是多,並且之間會相互調用,大多數都是外部引用的,不把js代碼直接寫在頁面上。這樣的話,若是有個domReady這個方法,咱們想用它就調用,無論邏輯代碼寫在哪裏,都是等到domReady以後去執行的。
window.onload方法,表示當頁面全部的元素都加載完畢,而且全部要請求的資源也加載完畢才觸發執行function這個匿名函數裏邊的具體內容。這樣確定保證了代碼在domReady以後執行。使用window.onload方法在文檔外部資源很少的狀況下不會有什麼問題,可是當頁面中有大量遠程圖片或要請求的遠程資源時,咱們須要讓js在點擊每張圖片時,進行相應的操做,若是此時外部資源尚未加載完畢,點擊圖片是不會有任何反應的,大大下降了用戶體驗。那既然window.onload方法不可行,又該怎麼作呢?
你確定想到了jquery中的$(document).ready(function(){})方法了,其實jquery中的domReady應該和window.onload的實現原理是大同小異的。爲了解決window.onload的短板,w3c 新增了一個 DOMContentLoaded 事件。
這裏提到了DOMContentLoaded事件,這裏因爲篇幅有限,就很少作介紹,這裏面也有不少細節能夠學習,有興趣的童鞋,能夠看看我以前收藏的兩篇文章:
你不知道的 DOMContentLoaded&version=12020110&nettype=WIFI&fontScale=100&pass_ticket=2uOqWTPNenLxF7wD%2F%2Bi%2F0TK60XMDQLdZ%2Bk2hyDjtKZsM9jitnQM4c%2B5cVfq0SJLP)
淺談DOMContentLoaded事件及其封裝方法
學習就是一個無底洞,由於深不可測,才讓人不斷探索。
參考jquery中domReady的實現原理,來看一下javascript中domReady的實現策略。
在頁面的DOM樹建立完成後(也就是HTML解析第一步完成)即觸發,而無需等待其餘資源的加載。即domReady實現策略:
1. 支持DOMContentLoaded事件的,就使用DOMContentLoaded事件。 2. 不支持的就用來自Diego Perini發現的著名Hack兼容。兼容原理大概就是經過IE中的document, documentElement.doScroll('left')來判斷DOM樹是否建立完畢。
JavaScript實現domReady,【domReady.js】
function myReady(fn){ //對於現代瀏覽器,對DOMContentLoaded事件的處理採用標準的事件綁定方式 if ( document.addEventListener ) { document.addEventListener("DOMContentLoaded", fn, false); } else { IEContentLoaded(fn); } //IE模擬DOMContentLoaded function IEContentLoaded (fn) { var d = window.document; var done = false; //只執行一次用戶的回調函數init() var init = function () { if (!done) { done = true; fn(); } }; (function () { try { // DOM樹未建立完以前調用doScroll會拋出錯誤 d.documentElement.doScroll('left'); } catch (e) { //延遲再試一次~ setTimeout(arguments.callee, 50); return; } // 沒有錯誤就表示DOM樹建立完畢,而後立馬執行用戶回調 init(); })(); //監聽document的加載狀態 d.onreadystatechange = function() { // 若是用戶是在domReady以後綁定的函數,就立馬執行 if (d.readyState == 'complete') { d.onreadystatechange = null; init(); } } } }
在頁面中引入donReady.js文件,引用myReady(回調函數)方法便可。
感興趣的童鞋能夠看看各個主流框架domReady的實現:點擊我查看
下面經過一個案例,來比較domReady與window.onload實現的不一樣,很明顯,onload事件是要在全部請求都完成以後才執行,而domReady利用hack技術,在加載完dom樹以後就能執行,因此domReady比onload執行時間更早,建議採用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> <img src="http://ww1.sinaimg.cn/large/ae49ba57gy1fe9zofelhdj20xc0xc42s.jpg" alt=""> <img src="http://ww1.sinaimg.cn/large/ae49ba57gy1fe9zofahw3j20m80etq4a.jpg" alt=""> <img src="http://ww1.sinaimg.cn/large/ae49ba57gy1fe9zoi3ny6j20l20dw4gd.jpg" alt=""> <img src="http://ww1.sinaimg.cn/large/ae49ba57gy1fe9zog3tauj20m80et0uw.jpg" alt=""> <img src="http://ww1.sinaimg.cn/large/ae49ba57gy1fe9zofi2o5j20m80ettaq.jpg" alt=""> <img src="http://ww1.sinaimg.cn/large/ae49ba57gy1fe9zohjuvhj20tb0cdwvp.jpg" alt=""> </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 += "時間戳:" + time1 + "<br>"; }); window.onload = function() { msgBox.innerHTML += "onload已加載!<br>"; time2 = new Date().getTime(); msgBox.innerHTML += "時間戳:" + time2 + "<br>"; msgBox.innerHTML += "domReady比onload快:" + (time2 - time1) + "ms<br>"; }; </script> </body> </html>
執行結果對比,發現DomReady比onload快樂2秒多。
爲何要判斷元素的節點?
由於要判斷元素節點類型,由於屬性的一系列操做與元素的節點類型息息相關,若是咱們不區分它們,咱們就不知道用元素的直接屬性操做(例如:ele.xxx=yyy)仍是用一個方法操做(el.setAttribute(xxx,yyy))。
設計元素類型的斷定,這裏給出有4個方法:
(1). isElement :斷定某個節點是否爲元素節點 (2). isHTML :斷定某個節點是否爲html文檔的元素節點 (3). isXML : 斷定某個節點是否爲xml文檔的元素節點 (4). contains :用來斷定兩個節點的包含關係
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>isElement</title> </head> <body> <div id="test">aaa</div> <!--這是一個註釋節點--> <script> var isElement = function (el){ return !!el && el.nodeType === 1; } var a = { //隨意定義一個變量,設置nodeType爲1 nodeType: 1 } console.log(isElement(document.getElementById("test"))); //結果: true console.log(isElement(document.getElementById("test").nextSibling)); //這裏的nextSibling屬性查找下一個相鄰節點,即註釋節點 //結果: false console.log(isElement(a)); //結果: true </script> </body> </html>
注意代碼中的!!用法:!!通常用來將後面的表達式轉換爲布爾型的數據(boolean).
由於javascript是弱類型的語言(變量沒有固定的數據類型)因此有時須要強制轉換爲相應的類型,關於JavaScript的隱式轉換,能夠看看以前我寫的一篇博客,這篇文章幾乎分析到了全部的轉換規則,感興趣的童鞋能夠點擊查閱,學習瞭解一下。
[從++[[]][+[]]+[+[]]==10?深刻淺出弱類型JS的隱式轉換](https://github.com/jawil/blog...
注意:上面的代碼定義了一個變量a,將它的nodeType的值設爲1,因爲元素節點的節點類型的數值常量爲1,因此這裏在打印的的時候,會將a認爲是元素節點,因此打印true。這種結果明顯不是咱們想要的,即便這種狀況不多出現。下面給出解決方案:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>isElement</title> </head> <body> <div id="test">aaa</div> <!--這是一個註釋節點--> <script> var testDiv = document.createElement('div'); var isElement = function (obj) { if (obj && obj.nodeType === 1) {//先過濾最簡單的 if( window.Node && (obj instanceof Node )){ //若是是IE9,則斷定其是否Node的實例 return true; //因爲obj多是來自另外一個文檔對象,所以不能輕易返回false } try {//最後以這種效率很是差但確定可行的方案進行斷定 testDiv.appendChild(obj); testDiv.removeChild(obj); } catch (e) { return false; } return true; } return false; } var a = { nodeType: 1 } console.log(isElement(document.getElementById("test"))); //結果: true console.log(isElement(document.getElementById("test").nextSibling)); //結果: false console.log(isElement(a)); //結果: false </script> </body> </html>
這樣,在判斷a是不是元素節點時,結果就是false了。
更多關於元素節點的判斷請參考:How do you check if a JavaScript Object is a DOM Object?
咱們能夠簡單的將全部的元素節點化爲兩類:一類是HTML,一類是XML。不過從嚴格意義上來講,HTML只是XML的一個子集,它擁有更多的特性,而XML在矢量繪圖的處理上又派生出了兩大類:SVG和VML。那麼按照這種方法,咱們能夠簡單的認爲若是不是HTML,就是XML的元素節點了。而HTML是比較容易識別的,由於它有更多的特性。好比說,XML是沒有className的,或者咱們經過一個元素的ownerDocument獲得它的文檔對象,XML是沒有document.getElementById()和document.getElementsByTagName()這些方法的.此外,最大的區別是HTML元素的nodeName老是大寫的,當你使用createElement()方法建立HTML元素的時候,不管你傳入的字母是大寫仍是小寫,最後獲得的都是大寫。
接下來就看看各大類庫是怎麼實現HTML和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"))); //但這樣不嚴謹,由於XML的根節點,也多是HTML標籤,好比這樣建立一個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>
瀏覽器隨便找個HTML頁面驗證一下:
<!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>
不過,這些方法都只是規範,javascript對象是能夠隨意添加的,屬性法很容易被攻破,最好是使用功能法。功能法的實現代碼以下:
<!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()方法,咱們斷定建立的元素的nodeName是區分大小寫的仍是不區分大小寫的,咱們就知道是XML仍是HTML文檔,這個方法是目前給出的最嚴謹的函數了。
判斷是否是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文檔的方法,咱們就能夠實現一個元素節點屬於HTML仍是XML文檔的方法了,實現代碼以下:
<!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的實例 return true; //因爲obj多是來自另外一個文檔對象,所以不能輕易返回false } try {//最後以這種效率很是差但確定可行的方案進行斷定 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>
DOM能夠將任何HTML描繪成一個由多層節點構成的結構。節點分爲12種不一樣類型,每種類型分別表示文檔中不一樣的信息及標記。每一個節點都擁有各自的特色、數據和方法,也與其餘節點存在某種關係。節點之間的關係構成了層次,而全部頁面標記則表現爲一個以特定節點爲根節點的樹形結構。DOM間的節點關係大體以下。
節點關係不只僅指元素節點的關係,document文檔節點也包含在內。在最新的瀏覽器中,全部的節點都已經裝備了contains()方法,
元素之間的包含關係,用自帶的contains方法,只有兩個都是元素節點,才能兼容各個瀏覽器,不然ie瀏覽器有的版本是不支持的,能夠採用hack技術,本身寫一個contains方法去兼容。
元素之間的包含關係:contains()方法.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>contains</title> </head> <body> <div id="p-node"> <div id="c-node">子節點內容</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">子節點內容</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>
DOM節點是一個很是複雜的東西,對它的每個屬性的訪問,不走運的話,就可能會向上溯尋到N多個原型鏈,所以DOM操做是個很是耗性能的操做。風頭正盛的react爲了解決這個問題,提出了虛擬DOM的概念,合併和屏蔽了不少無效的DOM操做,效果很是驚人。接下來看看DOM節點到底是如何繼承的。
使用document.createElement("p")建立p元素,其實document.createElement("p")是HTMLParagraphElement的一個實例,而HTMLParagraphElement的父類是HTMLElement,HTMLElement的父類是Element,Element的父類是Node,Node的父類是EventTarget,EventTarget的父類是Function,Function的父類是Object。
建立一個p元素一共溯尋了7層原型鏈:
下面咱們來分析一下建立一個元素所繼承的屬性分別是啥。
document.createElement("p")首先就是一個實例對象,它是由構造函數HTMLParagraphElement產生的,你能夠這麼看這個問題:
function HTMLParagraphElement() { [native code] } document.createElement("p")=new HTMLParagraphElement('p');
由以上繼承關係能夠看出來:
`document.createElement("p").constructor===HTMLParagraphElement
document.createElement("p").__proto__===HTMLParagraphElement.prototype
`
對實例對象,構造函數,以及JavaScript原型鏈和繼承不太熟悉的童鞋,該補習一下基礎看看了。
咱們先來看看document.createElement("p")自身有哪些屬性,遍歷對象屬性方法通常有三種:
先來說一講遍歷對象屬性三種方法的差別性,當作補充複習。
遍歷數組屬性目前我知道的有:for-in
循環、Object.keys()
和Object.getOwnPropertyNames()
,那麼三種到底有啥區別呢?
for-in循環:會遍歷對象自身的屬性,以及原型屬性,包括enumerable 爲 false(不可枚舉屬性); Object.keys():能夠獲得自身可枚舉的屬性,但得不到原型鏈上的屬性; Object.getOwnPropertyNames():能夠獲得自身全部的屬性(包括不可枚舉),但得不到原型鏈上的屬性,Symbols屬性 也得不到.
Object.defineProperty
顧名思義,就是用來定義對象屬性的,vue.js
的雙向數據綁定主要在getter
和setter
函數裏面插入一些處理方法,當對象被讀寫的時候處理方法就會被執行了。 關於這些方法和屬性的更具體解釋,能夠看MDN上的解釋(戳我);
簡單看一個小demo例子加深理解,對於Object.defineProperty
屬性不太明白,能夠看看上面介紹的文檔學習補充一下.
'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(); //設置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
有了上面的知識做爲擴充,咱們就能夠清晰明瞭的知道,建立元素P標籤每一步都繼承了哪些屬性,繼承對象自身有哪些屬性,因爲篇幅有限,你們能夠自行子在瀏覽器測試,看看這些對象的一些屬性和方法,便於咱們理解。
例如咱們想看:HTMLElement對象有哪些自身屬性,咱們能夠這麼查看:
Object.getOwnPropertyNames(HTMLElement)
咱們想看:HTMLElement的原型對象有哪些自身屬性,咱們能夠這麼查看:
Object.getOwnPropertyNames(HTMLElement.prototype)
HTMLElement的原型對象有哪些自身屬性,根據原型鏈,咱們也能夠這麼查看:
由於:document.createElement("p").__proto__.__proto__===HTMLElement.prototype
Object.getOwnPropertyNames(document.createElement("p").__proto__.__proto__)
使用document.createTextNode("xxx")建立文本節點,其實document.createTextNode("xxx")是Text的一個實例,而Text的父類是CharactorData,CharactorData的父類是Node,Node的父類是EventTarget,EventTarget的父類是Function,Function的父類是Object。
建立一個文本節點一共溯尋了6層原型鏈。
所以,全部節點的繼承層次都不簡單,但相比較而言,元素節點是更可怕的。從HTML1升級到HTML3.2,再升級到HTML4.1,再到HTML5,除了不斷地增長新類型、新的嵌套規則之外,每一個元素也不斷的添加新屬性。
下面看一個例子:建立一個p元素,打印它第一層原型的固有的屬性的名字,經過Object的getOwnPropertyNames()獲取當前元素的一些屬性,這些屬性都是他的原始屬性,不包含用戶自定義的屬性。
<!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>
下面看一個空的div元素,而且沒有插入到DOM裏邊,看它有多少自有屬性(不包括原型鏈繼承來的屬性)
在新的HTML規範中,許多元素的固有屬性(好比value)都放到了原型鏈當中,數量就更加龐大了。所以,將來的發展方向是儘可能使用現成的框架來實現,好比MVVM框架,將全部的DOM操做都轉交給框架內部作精細處理,這些實現方案固然就包括了虛擬DOM的技術了。可是在使用MVVM框架以前,掌握底層知識是很是重要的,明白爲何這樣作,爲何不這樣作的目的。這也是爲何要理解DOM節點繼承層次的目的。
HTML存在許多種類型的標籤,有的標籤下面只容許特定的標籤存在,這就叫HTML嵌套規則。
不按HTML嵌套規則寫,瀏覽器就不會正確解析,會將不符合嵌套規則的節點放到目標節點的下面,或者變成純文本。
關於HTML嵌套規則,必定要掌握塊狀元素和內聯元素的區別。
塊狀元素:通常是其餘元素的容器,可容納內聯元素和其餘塊狀元素,塊狀元素排斥其餘元素與其位於同一行,寬度(width)高度(height)起做用。常見塊狀元素爲div和p
內聯元素:內聯元素只能容納文本或者其餘內聯元素,它容許其餘內聯元素與其位於同一行,但寬度(width)高度(height)不起做用。常見內聯元素爲a.
塊狀元素與內聯元素嵌套規則:
(1).塊元素能夠包含內聯元素或某些塊元素,但內聯元素卻不能包含塊元素,它只能包含其餘的內聯元素
例: <div><h1></h1><p></p></div> <a href="#"><span></span></a>
(2).塊級元素不能放在<p>裏面
例:<p><ol><li></li></ol></p><p><div></div></p>
(3).有幾個特殊的塊級元素提倡只能包含內聯元素,不能再包含塊級元素,這幾個特殊的標籤是:
h一、h二、 h三、h四、 h五、 h六、 p 、dt
(4).li標籤能夠包含div標籤
例: <li><div></div></li>
(5).塊級元素與塊級元素並列,內聯元素與內聯元素並列
例: <div><h2></h2><p></p></div> <div><a href="#"></a><span></span></div>