前言:
本系列從原型,原型鏈,屬性類型等方面下手學習了DOM文檔對象模型,旨在弄清我們在DOM中常用的每一個屬性和方法都清楚它從哪裡來要到哪裡做什麼事,這樣對於理解代碼有一定啟發。全靠自己在總結中摸索,所以對於一個問題要是深究還真能挖出許多有意思的東西,現在覺得JavaScript這個東西簡直越來越有意思了。
正文:
DOM(文檔對象模型)是針對HTML和XML文檔的一個API(應用程序編程接口)。DOM描繪了一個層次化的節點樹,允許開發人員添加,移除,修改頁面某一部分,現在它已成為表現和操作頁面標記的真正的跨平台,語言中立的方式。
DOM1為基本文檔結構及查詢提供了接口。本系列主要討論JavaScript對DOM1級的實現,但是還會穿插一點DOM2和DOM3的內容。
節點層次
DOM可以將任何HTML和XML文檔描繪成一個由多層節點構成的結構。節點分為好幾種不同的類型,每種類型分別表示文檔中不同的信息及標記。每個節點都有各自特點,屬性和方法,及和其他節點存在的關系。節點之間的關系構成了層次,而所有頁面標記則表現為一個以特定節點為根節點的樹形結構。節點比元素的概念大,元素只是節點的一種類型。
文檔節點:每個文檔的根節點。HTML文檔中文檔節點( window.document=>#document )只有一個子節點即 HTML 元素。
文檔元素:文檔中最外層元素,文檔中的其他元素都包含在文檔元素中,每個文檔只能有一個文檔元素。在HTML頁面中,文檔元素始終都是 HTML 元素。XML中,沒有預定義的元素,任何元素都能成為文檔元素。
每一段標記都可通過樹中一個節點表示:HTML元素通過元素節點表示,特性通過特性節點表示,文檔類型通過文檔類型節點表示,注釋通過注釋節點表示...共有12種節點類型,這些類型都繼承自一個基類型Node類型。下面來挨個看這些節點類型,但是本篇著重學習Node類型,其他類型和DOM操作技術在後續系列的總結中。
DOM操作技術
Node類型:
DOM1級定義了一個Node接口,該接口將由DOM中所有節點類型實現。這個Node接口在JavaScript中作為Node類型實現,JavaScript中所有節點類型(Element,Attr,Text,CDATASection,Comment,Document,DocumentType,DocumentFragment等)都繼承自Node類型( Element.prototype instanceof Node;// true ),因此所有節點類型的實例都共享著原型鏈(某類型實例->某類型.prototype->Node.prototype->EventTarget.prototype->Object.prototype)上的屬性和方法, document instanceof Node;// true 比如文檔節點 #document 就是Document類型實例也是Node類型的實例,文檔元素 html 是HTMLElement類型的實例是Element類型實例也是Node類型的實例。
下面這段可以略過,只是我的一個小思考:
document.documentElement.__proto__==HTMLElement.prototype;// false //居然是false,這個html元素節點實例的原型指向的不是HTMLElement構造函數的prototype??但是... HTMLElement.prototype.isPrototypeOf(document.documentElement);// true //判斷HTMLElement.prototype確實是在html元素的原型鏈上啊
有沒有覺得很奇怪, __proto__ 不是指向構造這個實例的函數的原型嗎??為什麼會是 false ,而且 document.documentElement.__proto__ 和 HTMLelement.prototype 竟然不是同一類型的,按理說 html 元素作為HTMLElement的實例,默認它們指向同一個 HTMLElement.prototype 對象的。
百思不得其解,一度認為我對 __proto__ 這個東西是不是理解有誤,查看相關文檔MDN Object.prototype.__proto__後受了點啟發,
是不是 document.documentElement.__proto__ 被JS引擎重寫了!!?讓其重新指向一個為HTMLElement類型的實例對象(假設就叫 HTMLEleObj 吧,其實是 HTMLHtmlElement.prototype ),查看 HTMLEleObj 的 __proto__ ,發現這個東西類型為Element類型實例,想到 HTMLElement.prototype 也是Element類型的,那這兩個是不是同一個對象?

好像是這樣的: document.documentElement.__proto__.__proto__==HTMLElement.prototype;// true 。這也就能解釋為什麼html元素改變了 [[prototype]] 指向但還仍在原來的原型鏈上,JS引擎是給這個本身默認的原型鏈( html.__proto__->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype->Object.prototype )又增加了一個對象,現在原型鏈變成( html.__proto__->HTMLEleObj;HTMLEleObj.__proto__->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype->Object.prototype )。還是畫個圖比較好理解點。但是還是不明白為何JS引擎要在原型鏈上增加這麼個對象,有什麼用處?發現html元素身上有兩個屬性,版本和構造器,然而並不能直接通過 HTMLHtmlElement.prototype.version 訪問 version 屬性(每個元素的 __proto__ 除了 constructor 屬性外其余的這些屬性還都不一樣,不過都是HTML5之前元素上的屬性),需要通過它的實例(html元素)訪問。 constructor 指向 HTMLHtmlElement 接口。


---補充---
通過 document.documentElement.__proto__.constructor 訪問得到,HTMLEleObj對象其實是HTMLHtmlElement接口的原型對象,雖然以上思考有些誤解,但是還是留下這個思考的過程吧。真正的原型鏈是 某元素.__proto__->HTML某元素Element.prototype->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype->Object.prototype 。比如對於html元素就是 document.documentElement.__proto__->HTMLHtmlElement.prototype->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype->Object.prototype 。對於body元素就是 document.body.__proto__->HTMLBodyElement.prototype->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype->Object.prototype 。其實都是可以通過 某元素.__proto__.constructor 屬性訪問到其對應的構造器。
扯遠了,回歸主題來看Node類型:

Node.prototype上的屬性及方法



注意到 Node.prototype 是 EventTarget 類型的實例對象,怪不得 Node.prototype.__proto__ 會指向 EventTarget.prototype 。
Object.getOwnPropertyNames(EventTarget.prototype);// ["addEventListener", "removeEventListener", "dispatchEvent", "constructor"]

所有這些關系總結來說就是Node和EventTarget是JavaScript中兩種類型,用組合繼承模式實現的話就內部實現可能是這樣子:
function EventTarget(){
//初始化實例的屬性和方法
}
function Node(){
//初始化實例的屬性和方法
}
Node.prototype=new EventTarget();
Node.prototype.construct=Node;
//以Element類型舉例
Element.prototype=new Node();
Element.construct=Element;
...
Node.prototype 指向 EventTarget.prototype ,並且 Node.prototype 會被初始化上一些屬性和實例。不過事實上我們並不能成功構造 Node 和 EventTarget 類型的實例,控制台會提示報錯不合法的構造。那應該是JS引擎內部自己實現的吧,不然誰都能構造這種底層接口的實例那就亂套了。
Node.prototype常見屬性和方法:
這些關系指針屬性大部分都是只讀的因為訪問器屬性的 set 被設置為 undefiend 了,即使 set 不為 undefiend ,但 set 方法能被使用的前提是該節點的對應要訪問的那個屬性不為 null 。所以DOM提供了一些操作節點的方法(從第9小點開始總結,這些方法都是可寫的,並且在Node.prototype上可以重寫這些方法)

Node.prototype.COMMENT_NODE==Node.COMMENT_NODE;// true Node.prototype.COMMENT_NODE;// 8 Node.COMMENT_NODE;// 8 document.COMMENT_NODE;// 8應用:通過利用節點類型屬性可以確定節點的類型,為了兼容那些沒有公開Node類型的構造函數的浏覽器,我們就不用Node.ELEMENT_NODE形式訪問類型值,而是直接通過數字值判斷
if(someNode.nodeType==1){
console.log("這是一個元素節點");
}
nodeName和nodeValue屬性:
表示節點具體信息。
(1).對於Element元素節點:原型鏈繼承關系為:某元素節點.__proto__->HTML某元素Element.prototype->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype。
nodeName保存的為元素的標簽名,nodeValue的值為null。
var someNode=document.documentElement;// 獲取HTML元素
if(someNode.nodeType==1){
console.log(someNode.nodeName+"是元素節點名");
console.log("它的nodeValue:"+someNode.nodeValue);
}
/*HTML是元素節點名
它的nodeValue:null */
(2).對於Attr類型節點:原型鏈繼承關系為:某特性節點.__proto__->Attr.prototype->Node.prototype->EventTarget.prototype。
var html=document.documentElement; //獲取特性實例所在的對象 html.attributes;//attributes屬性是Element.prototype上的屬性

這個對象是NamedNodeMap類型的實例,這個對象的原型鏈關系為html.attributes.__proto__->NamedNodeMap.prototype->Object.prototype。這個對象裡面又有幾個屬性,這幾個屬性才是我們需要的真正特性對象。
html.attributes["0"];// lang="zh-cn" 是一個特性節點 html.attributes["1"];// style="overflow:hidden;" 是另一個特性節點 html.attributes["lang"].nodeName;// "lang" lang特性節點的nodeName值 html.attributes["lang"].nodeValue;// "zh-cn" html.attributes["0"] instanceof Attr;// true(3).對於Text類型節點:原型鏈的繼承關系為:某文本節點.__proto__->Text.prototype->CharacterData.prototype->Node.prototype->EventTarget.prototype。















返回 null 。等價的寫法是 nodeList[idx] , 不過這種情況下越界訪問將返回 undefined (因為是以數組形式訪問的)。
對arguments對象使用Array.prototype.slice()方法將其轉換為數組,采用同樣方法也可以將NodeList類型集合轉換為數組類型,其實就是就是在類數組對象的上下文中調用原生的slice方法。function transToArr(collections,start,end){
var length=arguments.length;
if(length==0){
return;
}else if(length==1){
return Array.prototype.slice.call(collections);
}else{
//判斷start,end類型
if(typeof arguments[1]=='number'){
if(typeof arguments[2]=='number'){
return Array.prototype.slice.call(collections,start,end);
}else{
//end參數不是number類型時,slice返回length之前的項
end=collections.length;
return Array.prototype.slice.call(collections,start,end);
}
}
}
}
(2)HTMLCollection接口是為一個包含了元素的通用集合,原型鏈的關系為:通過某用法(比如getElementsByTagName,getElementsByClassName,getElementsByTagNameNS,document.forms等)獲取的節點集合.__proto__->HTMLCollection.prototype->Object.prototype


//刪除childNodes中的所有文本節點,因為child.length是動態變化的,所以分情況i++
var child=parent.childNodes;
for(var i=0;i<child.length;){
if(child[i].nodeType==3){
ul.removeChild(child[i]); //不用i=0回歸到開時就用上次的i就可
}else{
i++;
}
}
但NodeList也有時候表現為靜態集合,以意味著對文檔對象模型任何改動都不會影響集合內容。querySelectorAll就是靜態的







var a=document.body.firstChild; document.body.appendChild(document.body.firstChild)==a;// true a==document.body.lastChild;// true
//要插入到desEle之後,相當於插入desEle.nextSibling之前,返回被插入的srcEle
Node.prototype.insertAfter=function(srcEle,desEle){
this.insertBefore(srcEle,desEle.nextSibling);
return srcEle;
}
var parent=$('#hdtb-msb');
var first=parent.firstChild;
var last=parent.lastChild;
var firstnode=parent.replaceChild(last,first);
firstnode;// <div class=?"hdtb-mitem hdtb-msel hdtb-imb">?全部?</div>?
firstnode.ownerDocument;// #document 節點 證明還在文檔樹
var clone1=last.cloneNode(true); clone1;// <a class=?"hdtb-tl" id=?"hdtb-tls" role=?"button" tabindex=?"0" data-ved=?"0ahUKEwiYht2SptbLAhVDao4KHSbJBqIQ2x8ICigF">?搜索工具?</a>? var clone2=last.cloneNode(false); clone2;// <a class=?"hdtb-tl" id=?"hdtb-tls" role=?"button" tabindex=?"0" data-ved=?"0ahUKEwiYht2SptbLAhVDao4KHSbJBqIQ2x8ICigF">?</a>? clone1.ownerDocument==clone2.ownerDocument // true
cloneNode()方法不會復制添加DOM節點的JavaScript屬性,例如事件處理程序。這個方法只復制特性(包括通過特性綁定的事件處理程序 <h1 onclick="console.log('xxx')">xxx</h1> 會將事件復制成功),子節點(深復制情況下),其他一切都不會復制。
document.createTextNode('');
document.createTextNode(' ');
document.createTextNode(' ');
...
也就是看該文本節點的data(ChacterData.prototype上的屬性)值就可以了,圖片上的data值是回車雖然在呈現上和空文本節點一樣,但並不是空所以不能被刪除了,所以注意這樣編代碼ul的childNodes裡的文本節點其實是回車符。
<ul>
<li></li>
<li></li>
</ul>
參考:
《JavaScript高級程序設計》
MDN HTMLCollection
MDN NodeList