DOM探索之基礎詳解

DOM爲document   object   model三個單詞的縮寫,直譯過來即文檔對象模型。

DOM的地位

我們知道,一個網頁是由html來搭建結構的,通過css來定義網頁的樣式,而javascript賦予了頁面的行爲,通過它我們可以與頁面進行交互,實現頁面的動畫效果等等。那javascript究竟通過什麼來實現的呢?通過ECMAScript這個標準,我們可以編寫程序讓瀏覽器來解析,利用ECMAScript,我們可以通過BOM對象(即browser object model)來操作瀏覽器窗口、瀏覽器導航對象、屏幕分辨率、瀏覽器歷史、cookie等等。但這個通過BOM來實現的交互遠遠不夠。要實現頁面的動態交互和效果,操作html纔是核心。那如何操作html呢?對,就是DOM,簡單的說,DOM給我們提供了用程序來動態控制html的接口,也就是早期的DHTMl的概念。因此,DOM處在javascript賦予html具備動態交互和效果的能力的核心地位上。

首先我們通過一個案例認識一下DOM在實際開發中是如何應用的;其次來了解什麼是DOM,以及與DOM相關的基礎概念;然後重新認識一下html與xml的兩種文檔類型以及文檔的各種節點類型;接着來詳細的實現DOMReady;接着瞭解如何判斷元素節點的類型以及什麼是元素節點的繼承層次;最後瞭解各種元素節點的分類和規則。

在深入學習DOM之前,先做一個熱身,做一個小案例感受一下DOM在web特效開發中是如何應用的。

DOM操作案例——滑動門特效

需求:把鼠標放到想要展示的圖片上,當前圖片伸展打開,其他的圖片堆疊關閉。

實現:搭建結構——定義樣式——着手編寫交互效果代碼(js+DOM)

效果圖


(1).搭建結構:index.html

<!doctype html>
<html lang="zh-CN">
	<head>
		<meta charset="UTF-8">
		<title>sliding doors</title>
		<link rel="stylesheet" href="styles/slidingdoors.css" />
		<script src="scripts/slidingdoors.js"></script>
	</head>
	<body>
		<div id='container'>
			<img src="images/door1.png" alt="1080P神器" title="1080P神器" />
			<img src="images/door2.png" alt="5.5寸四核" title="5.5寸四核" />
			<img src="images/door3.png" alt="四核5寸" title="四核5寸" />
			<img src="images/door4.png" alt="5.7寸機皇" title="5.7寸機皇" />
		</div>
	</body>
</html>

(2).定義樣式:slidingdoors.css

* {
	margin:0;
	padding:0;
}
#container {
	height: 477px;
	margin: 0 auto;
	border-right: 1px solid #ccc;
	border-bottom: 1px solid #ccc;
	overflow: hidden;
	position: relative;
}

#container img {
	position: absolute;
	display: block;
	left: 0;
	border-left: 1px solid #ccc;
}

(3).編寫交互效果:

第一步先定義頁面加載完畢再進行DOM操作的方法。

window.οnlοad=function(){

};

window.onload方法表示當頁面所有的元素都加載完畢,並且所有要請求的資源也加載完畢才觸發執行function這個匿名函數裏邊的具體內容。這裏的onload就是DOM範疇的其中一部分內容——事件。我們要實現和頁面進行交互,就要通過js來操作頁面元素或者說操作DOM,而要想操作DOM,就必須先找到DOM元素的節點,也就是要找到操作的對象

我們先來獲得div這個容器對象,再通過獲得的div對象,獲得四張圖片的對象集合。

//獲得容器對象

var box=document.getElementById("container");

//獲得圖片nodeList對象集合

var imgs=box.getElementsByTagName("img");

先看效果圖,當前狀態是第一張圖片打開,我們來看四張圖片的具體位置,第一張圖片的位置距離容器左側爲0;第二張圖片的位置距離容器左側爲一張圖片的寬度;第三張圖片的位置距離容器左側爲一張圖片的寬度+1個堆疊的寬度;第四張圖片的位置距離容器左側爲一張圖片的寬度+2個堆疊的寬度;以此類推。

返回代碼,根據剛纔直觀的查看,想要設置我們四張圖片的位置就需要知道單張圖片的寬度和需要定義我們堆疊的寬度

//獲得單張圖片的寬度(這裏的每張圖片大小一致,所以可以取得任意一張圖片獲得其寬度,這裏取第一張圖片imgs[0]的寬度)

var imgWidth=imgs[0].offsetWidth;

//設置掩藏門體露出的寬度

var exposeWidth=160;   //這個值可按需求自定義

我們返回到效果,我們要設置每張圖片的具體位置,我們有必要將我們的總盒子的寬度設置爲他應該的值,這樣的話,我們的盒子才正好的將我們所有的圖片包含在內。那麼我們盒子的總寬度應該等於我們單張打開的圖片的寬度+我們剩餘的圖片的堆疊的寬度的和。

回到代碼

//設置容器總寬度

var boxWidth=imgWidth+(imgs.length-1)*exposeWidth;

box.style.width=boxWidth+'px';

寫到這,我們可以先看看效果。目前我們四張圖片還是堆疊在一起,但容器的寬度已經設置好了,下面我們就讓圖片歸位。

回到代碼,我們來設置每道門的初始位置,這裏通過for循環一併爲所有的圖片來進行定位。由於第一道門距離容器左側爲0,我們無需設置它的位置,因此我們循環的累加變量初始值爲1。

//設置每道門的初始位置

function setImgsPos(){

for(var i=1,len=imgs.length;i<len;i++){

    imgs[i].style.left=imgWidth+exposeWidth*(i-1)+'px';

}

}

setImgsPos();         //需要調用執行一下

現在我們來瀏覽器中看一下效果,此時四張圖片已經歸位,下面我們來實現門體的滑動。鼠標滑過以後,每個應該滑動的門體滑動的距離是多少呢?我們仔細觀察不難看出,這個距離就是我們圖片的寬度減去我們堆疊露出的寬度。

回到代碼,我們首先計算每道門打開時應移動的距離

//計算每道門打開時應移動的距離

var translate=imgWidth-exposeWidth;

//爲每道門綁定事件

for(var i=0,len=imgs.length;i<len;i++){

    //使用立即調用的函數表達式,爲了獲得不同的i值

    (function(i){

        imgs[i].οnmοuseοver=function(){

            //先將每道門復位

            setImgsPos();

        };

    })(i);

}

下面我們來實現開門滑動的效果,回到瀏覽器觀察一下,當我們所有的圖片都歸位以後,當我們鼠標滑到我們想打開的門的時候,比如說第三張,我們只需要將第二張和第三張圖片向左滑動即可,其他圖片的滑動規律類似。比如我們放到第四張,那麼我們需要將第二張、第三張、第四張圖片都向左滑動,這就是我們圖片滑動的規律。根據得出的規律,我們要實現打開門,只需要處理第二張到當前圖片之間所有的圖片即可。

回到代碼

//爲每道門綁定事件

for(var i=0,len=imgs.length;i<len;i++){

    //使用立即調用的函數表達式,爲了獲得不同的i值

    (function(i){

        imgs[i].οnmοuseοver=function(){

            //先將每道門復位

            setImgsPos();

            //打開門

            for(var j=1;j<=i;j++){

                imgs[j].style.left=parseInt(imgs[j].style.left,10)-translate+'px';

            }

        };

    })(i);

}

到此,這個案例的js代碼和DOM操作的代碼就編寫完畢了。

完整的js代碼:slidingdoors.js

window.onload = function() {
	//容器對象
	var box = document.getElementById('container');

	//獲得圖片NodeList對象集合
	var imgs = box.getElementsByTagName('img');

	//單張圖片的寬度
	var imgWidth = imgs[0].offsetWidth;

	//設置掩藏門體露出的寬度
	var exposeWidth = 160;

	//設置容器總寬度
	var boxWidth = imgWidth + (imgs.length - 1) * exposeWidth;
	box.style.width = boxWidth + 'px';

	//設置每道門的初始位置
	function setImgsPos() {
		for (var i = 1, len = imgs.length; i < len; i++) {
			imgs[i].style.left = imgWidth + exposeWidth * (i - 1) + 'px';
		}
	}
	setImgsPos();

	//計算每道門打開時應移動的距離
	var translate = imgWidth - exposeWidth;

	//爲每道門綁定事件
	for (var i = 0, len = imgs.length; i < len; i++) {
		//使用立即調用的函數表答式,爲了獲得不同的i值
		(function(i) {
			imgs[i].onmouseover = function() {
				//先將每道門復位
				setImgsPos();
				//打開門
				for (var j = 1; j <= i; j++) {
					imgs[j].style.left = parseInt(imgs[j].style.left, 10) - translate + 'px';
				}
			};
		})(i);
	}
};	

認識DOM

DOM(文檔對象模型)是針對xml經過擴展用於html的應用程序編程接口,我們又叫API。DOM把整個頁面映射爲一個多層的節點結構,html或xml頁面中的每個組成部分都是某種類型的節點,這些節點又包含着不同類型的數據。

DOM級別

(1).DOM1級:由兩個模塊組成,即DOM  Core(DOM核心)和DOM HTML。其中DOM Core規定的是如何映射基於xml的文檔結構,以便簡化對文檔中任何部分的訪問和操作;DOM HTML模塊則在DOM Core的基礎上加以擴展,添加了針對html的對象和方法。

其實DOM並不是針對javascript,很多別的語言也都實現了DOM,不過在web瀏覽器中基於ECMAScript實現的DOM的確已經成爲javascript這門語言的一個重要組成部分。如果說DOM1級的目標主要是映射文檔的結構,那麼DOM2級的目標就要寬泛很多了。

(2).DOM2級:由四個模塊組成,即DOM Views(DOM 視圖)、DOM Events(DOM事件)、DOM Style、DOM Traversal and Range(DOM遍歷和範圍),DOM Views定義了跟蹤不同文檔視圖的接口,比如跟蹤應用css之前和應用css之後的文檔視圖;DOM Events定義了事件和事件處理的接口;DOM Style定義了基於css爲元素應用樣式的接口;DOM Traversal and Range定義了遍歷和操作文檔樹的接口。

(3).DOM3級:進一步擴展了DOM,引入了以統一方式加載和保存文檔的方法,它在DOM Load And Save這個模塊中定義;同時新增了驗證文檔的方法,是在DOM Validation這個模塊中定義的。

我們在閱讀DOM標準的時候,經常會看到DOM0級這樣的字眼,實際上DOM0級這個標準是不存在的。所謂DOM0級只是DOM歷史座標系中的一個參照點而已,具體地說DOM0級就是指IE4.0和Netscape navigator4.0最初支持的那個DHTML。

Web瀏覽器對DOM的支持
瀏覽器 DOM兼容性
 Netscape Navigator 1.~4.x                       -
 Netscape 6+(Mozilla 0.6.0+)
1級、2級(幾乎全部)、3級(部分)
IE2~IE4.x
-
IE5
1級(最小限度)
IE5.5~IE8
1級(幾乎全部)
IE9+
1級、2級、3級
Opera 1~6
-
Opera7~8.x
1級(幾乎全部)、2級(部分)
Opera9~9.9
1級、2級(幾乎全部)、3級(部分)
Opera 10+
1級、2級、3級(部分)
Safari 1.0.x
1級
Safari 2+
1級、2級(部分)
Chrome 1+
1級、2級(部分)
Firefox 1+
1級、2級(幾乎全部)、3級(部分)


DOM文檔類型


我們說DOM文檔對象模型是從文檔中抽象出來的,DOM操作的對象也是文檔,因此我們有必要了解一下文檔的類型。文檔隨着歷史的發展演變爲多種類型,如下



DOM節點類型

DOM1級定義了一個Node接口,這個Node接口在javascript中是作爲Node類型來實現的。除了IE以外,其他所有瀏覽器都可以訪問這個類型。每個節點都有一個nodeType屬性,用於表明節點的類型。節點類型通過定義數值常量和字符常量兩種方式來表示,IE只支持數值常量。節點類型一共有12種,這裏介紹常用的7種類型。如下圖:



(1).Element(元素節點):是組成文檔樹的重要部分,它表示了html、xml文檔中的元素。通常元素因爲有子元素、文本節點或者兩者的結合,元素節點是唯一能夠擁有屬性的節點類型。

(2).Attr(屬性節點):代表了元素中的屬性,因爲屬性實際上是附屬於元素的,因此屬性節點不能被看做是元素的子節點。因而在DOM中屬性沒有被認爲是文檔樹的一部分。換句話說,屬性節點其實被看做是包含它的元素節點的一部分,它並不作爲單獨的一個節點在文檔樹中出現。

(3).Text(文本節點):是隻包含文本內容的節點,在xml中稱爲字符數據,它可以由更多的信息組成,也可以只包含空白。在文檔樹中元素的文本內容和屬性的文本內容都是由文本節點來表示的。

(4).Comment(註釋節點):表示註釋的內容

(5)5.Document(文檔節點) :是文檔樹的根節點,它是文檔中其他所有節點的父節點。要注意的是,文檔節點並不是html、xml文檔的根元素,因爲在xml文檔中,處理指令、註釋等內容可以出現在根元素之外,所以我們在構造DOM樹的時候,根元素並不適合作爲根節點,因此就有了文檔節點,而根元素是作爲文檔節點的子節點出現的。

(6).DocumentType(文檔類型節點):每一個Document都有一個DocumentType屬性,它的值或者是null,或者是DocumentType對象。比如聲明文檔類型時<!doctype html>就是文檔類型節點。

(7).DocumentFragment(文檔片段節點):是輕量級的或最小的Document對象,它表示文檔的一部分或者是一段,不屬於文檔樹。不過它有一種特殊的行爲,該行爲使得它非常有用。比如:當請求把一個DocumentFragment節點插入到文檔的時候,插入的不是DocumentFragment自身,而是它的所有的子孫節點。這使得DocumentFragment成了有用的佔位符,暫時存放那些一次插入文檔的節點,同時它還有利於實現文檔的剪切、複製和粘貼等操作。實例代碼如下。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
	<title>DocumentFragment文檔片段節點</title>
</head>
<body>
	<ul id="list-node"></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>
這段代碼在瀏覽器查看元素時就會發現,頁面中並沒有插入DocumentFragment節點自身,它只作爲一個臨時佔位符存在。

(1).DOM nodeType

通過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中只支持數值常量,其他瀏覽器數值常量和字符常量都支持,因此可以直接用數值常量判斷,
		 *這裏爲了比較兩種寫法,便都寫在了這裏
		*/
		if (divNode.nodeType == Node.ELEMENT_NODE || divNode.nodeType == 1) {
			alert("Node is an element.");
		}		
	</script>
</body>
</html>


(2).DOM nodeName和nodeValue

先看示例代碼

<!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>

根據實驗,得出以下彙總表格。

節點中的nodeName和nodeValue


domReady

(1).什麼是domReady?

html是一種標記語言,它告訴我們這個頁面有什麼內容,但行爲交互是需要通過DOM操作來實現的。我們不要以爲有兩個尖括號就以爲它是一個DOM了,html標籤要通過瀏覽器解析纔會變成DOM節點,當我們向地址欄傳入一個url的時候,我們開始加載頁面,就能看到內容,在這期間就有一個DOM節點構建的過程。節點是以樹的形式組織的,當頁面上所有的html都轉換爲節點以後,就叫做DOM樹構建完畢,簡稱爲domReady。

(2).那麼瀏覽器是如何將html標籤解析變成DOM節點的呢?

實際上瀏覽器是通過渲染引擎來實現的。渲染引擎的職責就是把請求的內容顯示到瀏覽器屏幕上。默認情況下渲染引擎可以顯示html、xml文檔及圖片。通過插件(瀏覽器擴展)它可以顯示其他類型的文檔,比如我們安裝pdf viewer插件,我們就可以顯示pdf文檔。這裏專注渲染引擎的主要用途,即是將css格式化的html和圖片在瀏覽器上進行顯示。

(3).瀏覽器渲染引擎的基本渲染流程

渲染引擎首先通過網絡獲得所請求文檔的內容,通常以8k分塊的方法來完成

瀏覽器渲染引擎的基本渲染流程



上圖就是html渲染的基本過程,但這並不包含解析過程中瀏覽器加載外部資源,比如圖片、腳本、iframe等的一些過程。說白了,上面的4步僅僅是html結構的渲染過程。而外部資源的加載在html結構的渲染過程中是貫徹始終的,即便繪製DOM節點已經完成,而外部資源仍然可能正在加載或者尚未加載。

Webkit主要渲染流程


更多關於瀏覽器內部工作原理的文章,請閱讀:http://kb.cnblogs.com/page/129756/

(4).domReady的實現策略

上面的各個代碼實例中,並沒有考慮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 事件。
參考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與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="1.jpg" />
      <img src="2.jpg" />
      <img src="3.jpg" />
      <img src="4.jpg" />
      <img src="5.jpg" />
      <img src="6.jpg" />
    </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>
執行結果



如何判斷元素節點類型

設計元素類型的判定,這裏給出有4個方法:
(1).  isElement  :判定某個節點是否爲元素節點
(2).  isHTML     :判定某個節點是否爲html文檔的元素節點
(3).  isXML       : 判定某個節點是否爲xml文檔的元素節點
(4).  contains   :用來判定兩個節點的包含關係

(1).元素節點的判定:isElement  

<!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是弱類型的語言(變量沒有固定的數據類型)所以有時需要強制轉換爲相應的類型,類似的如: 
a=parseInt("1234");
a="1234"+0 //轉換爲數字 
b=1234+"" //轉換爲字符串 
c=someObject.toString() //將對象轉換爲字符串 
其中第1種、第4種爲顯式轉換,2、3爲隱式轉換.
布爾型的轉換,javascript約定和c類似,規則爲 :
false、undefinded、null、0、"" 爲 false ;
true、1、"somestring"、[Object] 爲 true .
!!el表示判斷el是否存在,存在爲true,反之爲false.
注意:上面的代碼定義了一個變量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了。


(2).HTML文檔元素節點的判定和XML文檔元素節點的判定:isHTML     and      isXML  

我們可以簡單的將所有的元素節點化爲兩類:一類是HTML,一類是XML。不過從嚴格意義上來說,HTML只是XML的一個子集,它擁有更多的特性,而XML在矢量繪圖的處理上又派生出了兩大類:SVG和VML。那麼按照這種方法,我們可以簡單的認爲如果不是HTML,就是XML的元素節點了。而HTML是比較容易識別的,因爲它有更多的特性。比如說,XML是沒有className的,或者我們通過一個元素的ownerDocument得到它的文檔對象,XML是沒有document.getElementById()和document.getElementsByTagName()這些方法的.此外,最大的區別是HTML元素的nodeName總是大寫的,當你使用createElement()方法創建HTML元素的時候,無論你傳入的字母是大寫還是小寫,最後得到的都是大寫。
接下來就看看各大類庫是怎麼實現HTML和XML文檔的元素節點的判定的。

(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")));

		//但這樣不嚴謹,因爲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>

(2).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>

不過,這些方法都只是規範,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>

(3).判斷節點的包含關係

節點關係不僅僅指元素節點的關係,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>

繼承層次與嵌套規則

(1).DOM節點繼承層次
DOM節點是一個非常複雜的東西,對它的每一個屬性的訪問,不走運的話,就可能會向上溯尋到N多個原型鏈,因此DOM操作是個非常耗性能的操作。風頭正盛的react爲了解決這個問題,提出了虛擬DOM的概念,合併和屏蔽了很多無效的DOM操作,效果非常驚人。接下來看看DOM節點究竟是如何繼承的。
(1).例如使用document.createElement("p")創建p元素,其實document.createElement("p")是HTMLParagraphElement的一個實例,而HTMLParagraphElement的父類是HTMLElement,HTMLElement的父類是Element,Element的父類是Node,Node的父類是EventTarget,
EventTarget的父類是Function,Function的父類是Object。
創建一個p元素一共溯尋了7層原型鏈
(2).例如使用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裏邊,看它有多少自有屬性(不包括原型鏈繼承來的屬性)

空的DIV元素的自有屬性



在新的HTML規範中,許多元素的固有屬性(比如value)都放到了原型鏈當中,數量就更加龐大了。因此,未來的發展方向是儘量使用現成的框架來實現,比如MVVM框架,將所有的DOM操作都轉交給框架內部做精細處理,這些實現方案當然就包括了虛擬DOM的技術了。但是在使用MVVM框架之前,掌握底層知識是非常重要的,明白爲什麼這樣做,爲什麼不這樣做的目的。這也是爲什麼要理解DOM節點繼承層次的目的。

(2).HTML嵌套規則

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).有幾個特殊的塊級元素提倡只能包含內聯元素,不能再包含塊級元素,這幾個特殊的標籤是
h1、h2、 h3、h4、 h5、 h6、 p 、dt
(4).li標籤可以包含div標籤
例:
<li><div></div></li>
(5).塊級元素與塊級元素並列,內聯元素與內聯元素並列
例:
<div><h2></h2><p></p></div>
<div><a href="#"></a><span></span></div>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章