前言:html
本系列從原型,原型鏈,屬性類型等方面下手學習了DOM文檔對象模型,旨在弄清咱們在DOM中經常使用的每個屬性和方法都清楚它從哪裏來要到哪裏作什麼事,這樣對於理解代碼有必定啓發。全靠本身在總結中摸索,因此對於一個問題要是深究還真能挖出許多有意思的東西,如今以爲JavaScript這個東西簡直愈來愈有意思了。node
正文:
DOM(文檔對象模型)是針對HTML和XML文檔的一個API(應用程序編程接口)。DOM描繪了一個層次化的節點樹,容許開發人員添加,移除,修改頁面某一部分,如今它已成爲表現和操做頁面標記的真正的跨平臺,語言中立的方式。
DOM1爲基本文檔結構及查詢提供了接口。本系列主要討論JavaScript對DOM1級的實現,可是還會穿插一點DOM2和DOM3的內容。編程
節點層次數組
DOM能夠將任何HTML和XML文檔描繪成一個由多層節點構成的結構。節點分爲好幾種不一樣的類型,每種類型分別表示文檔中不一樣的信息及標記。每一個節點都有各自特色,屬性和方法,及和其餘節點存在的關係。節點之間的關係構成了層次,而全部頁面標記則表現爲一個以特定節點爲根節點的樹形結構。節點比元素的概念大,元素只是節點的一種類型。瀏覽器
文檔節點:每一個文檔的根節點。HTML文檔中文檔節點( window.document=>#document )只有一個子節點即 HTML 元素。緩存
文檔元素:文檔中最外層元素,文檔中的其餘元素都包含在文檔元素中,每一個文檔只能有一個文檔元素。在HTML頁面中,文檔元素始終都是 HTML 元素。XML中,沒有預約義的元素,任何元素都能成爲文檔元素。app
每一段標記均可經過樹中一個節點表示: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
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 */
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
返回 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); } } } }
//刪除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++; } }
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高級程序設計》