歡迎光臨
每天分享高質量文章

通俗易懂的來講講DOM

DOM是所有前端開發每天打交道的東西,但是隨著jQuery等庫的出現,大大簡化了DOM操作,導致大家慢慢的“遺忘”了它的本來面貌。不過,要想深入學習前端知識,對DOM的瞭解是不可或缺的,所以本文力圖系統的講解下DOM的相關知識,如有遺漏或錯誤,還請大家指出一起討論^ ^。
一、DOM是什麼?

DOM(檔案物件模型)是針對HTML和XML檔案的一個API,透過DOM可以去改變檔案。

這個說法很官方,大家肯定還是不明白。

舉個例子:我們有一段HTML,那麼如何訪問第二層第一個節點呢,如何把最後一個節點移動到第一個節點上面去呢?

DOM就是定義瞭如果做類似操作,那麼應該怎麼做的標準。比如用getElementById來訪問節點,用insertBefore來插入節點。

當瀏覽器載入HTML時,會生成相應的DOM樹。

簡而言之,DOM可以理解為一個訪問或操作HTML各種標簽的實現標準。

對於一個HTML來說,檔案節點Document(看不到的)是它的根節點,對應的物件便是document物件(嚴格講是子類HTMLDocument物件,下麵單獨介紹Document型別時會指出)。

換句話說存在一個檔案節點Document,然後它有子節點,比如透過document.getElementsByTagName(“html”),得到型別為元素節點的Element html。

每一段HTML標記都可以用相應的節點表示,例如:

HTML元素透過元素節點表示,註釋透過註釋節點表示,檔案型別透過檔案型別節點表示等。

一共定義了12種節點型別,而這些型別又都繼承自Node型別。

所以我們首先講Node型別,因為這個型別的方法是所有節點都會繼承的。

二、Node型別(基類,所有節點都繼承了它的方法)

Node是所有節點的基型別,所有節點都繼承自它,所以所有節點都有一些共同的方法和屬性。

先講Node型別的屬性

首先是nodeType屬性,用來表明節點型別的,例如:

document.nodeType; // 傳回 9 ,其中document物件為檔案節點Document的實體

這裡面,9代表的就是DOCUMENT_NODE節點的意思,可以透過Node.DOCUMENT_NODE檢視節點對應的數字

document.nodeType === Node.DOCUMENT_NODE; // true

至於一共有哪些節點,每個節點對應的數字又是多少,這個可以問谷歌就知道了。反正常用的就是元素節點Element(對應數字為1)和文字節點Text(對應數字為3)

然後常用的還有nodeName和nodeValue

對於元素節點 nodeName就是標簽名,nodeValue就是null

對於文字節點 nodeName為”#text”(chrome裡面測試的),nodeValue就是實際的值

每個節點還有childNodes屬性,這是個十分重要的屬性,它儲存了這個節點所有直接子元素

呼叫childNodes傳回的是一個NodeList物件,它極其像陣列,但是有一個最關鍵的地方,它是動態查詢的,也就是說每次呼叫它都會對DOM結構查詢,所以對它的使用需要慎重,註意效能。

訪問childNodes可以使用陣列下表或者item方法

然後各個節點還存在各種屬性讓它們可以相互訪問,下圖很好的總結了

比較有用的方法和屬性:

1、hasChildNodes()

如果包含子節點就傳回true,比查詢childNodes的length來的簡單。

2、ownerDocument

傳迴檔案節點的取用(在html裡面也就是document物件)

再介紹下Node型別常用的方法

appendChild()方法可以在節點的childNodes的末尾增加一個節點,值得註意的是如果這個節點是已經存在在檔案中的,那麼便會刪除原節點,感覺上就像是移動節點一樣。

insertBefore()方法接受兩個引數,一個是插入的節點,另外一個是參照的節點。如果第二個引數為null,則insertBefore和appendChild效果一樣。否則便會把節點插入到參照節點之前。這裡要註意的是,如果第二個引數不為null,那麼插入的節點不能是已經存在的節點。

replaceChild()方法可以替換節點,接受兩個引數,需要插入的節點和需要替換的節點。傳回被替換掉的節點。

removeChild()移除節點。這裡有個常見需求,比如我有一個節點 #waste-node ,那麼如何移除它呢?

var wasteNode = document.getElementById(“waste-node”);

wasteNode.parentNode.removeClhid(wasteNode); // 先拿到父節點,再呼叫removeClild刪除自己

這裡先暫停一下,不知道大家註意到沒有,以上的幾個方法都是操作某個節點的子節點,也就是說,操作前必須找到父節點(透過parentNode來找)

接下來說下複製節點的方法:

cloneNode();複製節點,接受一個引數 true或者false。如果true就是複製那個節點和它的子節點。如果是false,就是複製節點本身(複製出來的節點就會沒有任何子元素)。這個方法傳回覆制的節點,如果需要操作它,那麼需要藉助前面講的4個方法來把這個節點放入到html中去。

至此,Node型別的常見屬性和方法都介紹完了。結合開頭講的,所有節點型別都繼承自Node型別,所以這些方法是所有節點都有的。

三、Document型別

最開始講DOM是什麼的時候提到了Document型別。其實關於這個型別最重要的是它的一個子類HTMLDocument有一個實體物件document。而這個document物件是我們最常用的一個物件了。

document物件又掛載在window物件上,所以在瀏覽器就可以直接訪問document了。

老規矩,先講講document物件的屬性,等會講講它的方法。

document物件上的一些屬性

document.childNodes 繼承自上面講的Node型別,可以傳迴檔案的直接子節點(通常包括檔案宣告和html節點)

document.documentElement 可以直接拿到html節點的取用(等價於document.getElementsByTagName(“html”)[0])。

document.body body節點的取用

document.title 頁面的title,可以修改,會改變瀏覽器標簽上的名字

document.URL 頁面的url

document.referrer 取得referrer,也就是開啟這個頁面的那個頁面的地址,做來源統計時候比較有用

document.domain 取得域名,可以設定,但是通常只能設定為不包含子域名的情況,在一些子域名跨域情況下有效。

接下來介紹兩個熟悉的方法

getElementById 和 getElementsByTagName

getElementById,傳入id,得到元素節點。裡面的引數區分大小寫(IE8-不區分)。註意:如果有多個id相同的元素,則傳回第一個。IE7-裡面表單元素的name也會被當做id來使用。

getElementsByTagName 根據標簽取得元素,得到的是HTMLCollection型別。如果傳入的是 “*” ,則可以取得全部元素。

還有一個是隻有HTMLDocument型別(也就是document物件)才有的方法 getElementsByName 顧名思義,根據name傳回元素。

document物件還有一些集合,例如document.forms 可以傳回所有的form表單。型別也是HTMLCollection。

說到HTMLCollection,就再說說它

HTMLCollection就是一個包含一個或多個元素的集合,和上面講的NodeList還挺像的。HTMLCollection這個型別有兩個方法,一個是透過下標(或者.item())得到具體元素,還有就是透過[‘name’](或者.namedItem())獲得具體元素。

最後,關於document物件還有一套重要的方法,那便是

write() writeln() open() close()

open和close分別是開啟和關閉網頁的輸出流,在頁面載入過程中,就相當於open狀態。這兩個方法一般不會去用它。

然後重要的方法就是write和writeln,它們都是向頁面寫入東西,區別就是後者會多加入一個換行符。

註意的是:在頁面載入的過程中,可以使用這兩個方法向頁面新增內容。如果頁面已經載入完了,再呼叫write,會重寫整個頁面。

還有一點,如果要動態寫入指令碼 例如 這樣的 ,那麼要註意把

分開來拼裝下,否則會被誤以為是指令碼結束的標誌,導致這個結束符匹配到上面一個開始符。可以這樣寫””;

四、Element型別

接下來講講最重要也是最常見的一個型別,Element型別。

我們日常所操作的都是Element型別(實質是HTMLElement,這裡為了方便理解,就簡單這麼說),比如

document.getElementById(“test”)

傳回的就是Element型別。我們日常所說的“DOM物件”,通常也就是指Element型別的物件。

然後說說這個型別的常見屬性:

首先最開始說的Node型別上的那些屬性方法它都有,這個就不再重覆了,主要說說它自己獨有的。

首先是tagName,這個和繼承自Node型別的nodeName一樣。都是傳回標簽名,通常是大寫,結果取決於瀏覽器。所以在做比較

的時候最好是呼叫下類似toLowerCase()這種方法再做比較。

說說上面提到過的HTMLElement型別

HTMLElement型別繼承自Element型別,也是HTML元素的實際型別,我們在瀏覽器裡用的元素都是這個型別。

這個型別都具有一些標準屬性,比如:

id 元素的唯一標識

title 通常是滑鼠移上去時候會顯示的資訊

className 類名

等等,這幾個屬性是可讀寫的,也就是說你改變他們會得到相應的效果。

除了屬性外,還有幾個重要的方法

首先說說操作節點屬性的方法

getAttribute 、setAttribute 、removeAttribute這3個方法。

這些是操作屬性最常用的方法了,怎麼用就不說了,很簡單,顧名思義。

還有一個attributes屬性,儲存了元素的全部屬性。

這裡停下來,出個問題,ele.className 和 ele.getAttribute(“class”)傳回的結果是不是同一個東西?

解答這個問題,我要說一個重要知識點,一個元素的屬性結構是這麼來的,比如一個inpnt元素

那麼這個元素的屬性被包含在 input.attributes裡面,比如你在html元素上看到的class、id或者你自己定義的data-test這種屬性。

然後 getAttribute 、setAttribute 、removeAttribute這3個方法可以認為是快捷的取attributes集合的方法。而直接input.id或者input.className都是直接掛在input下的屬性,和attributes是同級的。所以傳回的東西也許看過去一樣,實際是不一樣的,不信你可以試試input.checked這input.getAttribute(“checked”)試試。

關於這個知識點,詳細的說可以再寫一篇文章,在我的部落格 從is(“:checked”)說起 中有談到過,大家可以看看這篇文章和文章後的討論,便可以知道是怎麼一回事。

總得來說,這3個方法通常用了處理自定義的屬性,而不是id、class等這種“公認特性”。

接下來說說建立元素

document.createElement()可以建立一個元素,比如:

document.createElement(“div”);

一般之後可以為元素設定屬性,兩種方法,一種是直接node.property還可以node.setAttribute(“propertyName”,”value”)。等

但是做完這些之後,這個元素還是沒有在頁面中,所以你還得透過最上面講的類似appendChild這些方法把元素新增到頁面裡面。

在IE中,還可以直接穿整個HTML字串進去,來建立元素,比如

document.createElement(”

test

“);

最後,元素節點也支援HTMLDocument型別的那些查詢方法,比如getElementsByTagName。不過它只會找自己後代的節點。所以可以這麼寫程式碼

document.getElementById(“test”).getElementsByTagName(“div”); // 找到id為test元素下的所有div節點

五、Text型別

這個型別很特殊,也是第三常見型別(第一第二分別就是Document和Element)。

這個節點簡單來說就是一段字串。

有個很重要的特徵就是,它沒有子元素(不過這個仔細想想也知道= =)

訪問text節點的文字內容,可以透過nodeValue或者data屬性。

下麵簡單說說它提供的一些方法

appendData(); // 在text末尾加內容

deleteData(offset, count); // 從offset指定的位置開始刪除count個字元

還有insertDate、replaceData、splitText等方法,就不一一說了,用的機會很少,可以用的時候再查閱。

然後它還有一個lenght屬性,傳回字符長度的。

這裡說一個常見的坑。比如下麵這個html結構

這裡,ul的第一個子節點(firstChild)是什麼呢?第一眼看過去,肯定認為是li了,但是實際上,你會發現不是li,而是一個文字節點!

這是因為瀏覽器認為ul和第一個li之間有空白字元,所以就有文字節點了。

這裡一個常見的問題就是遍歷ul的childNodes的時候,遍歷的元素一定要判斷下nodeType是不是等於1(等於1就代表是元素節點),這樣才能跳過這個坑。否則你也可以刪除所有的空格和換行符。

建立文字節點的方法是document.createTextNode

然後接下來和操作Element型別一樣,就是再插入到元素中,瀏覽器就可以看到了。

六、其他的一些型別 Comment、DocumentType和DocumentFragment

這些不常用的一句話帶過把

Comment是註釋節點

DocumentType就是doctype節點,透過docment.doctype來訪問

DocumentFragment這個節點是一個檔案片段,偶爾會用到。

比如一種常見的用法是,在一個ul中插入3個li。

如果你迴圈插入3次,那麼瀏覽器就要渲染3次,對效能有蠻大的影響。

所以大家一般這麼做

var fragment = document.createDocumentFragment();

然後迴圈把li,用appendChild插入到fragment裡面

最後在一次把fragment插入到ul裡面。這樣就會很快。

七、DOM擴充套件

進過上面講的這麼多節點型別,想必大家對DOM節點已經有了很深的瞭解,下麵講一講DOM擴充套件的一些東西。

瀏覽器為了方便開發者,擴充套件了一些DOM功能。

因為是瀏覽器自己擴充套件的,所以使用前相容性問題一定要註意

判斷“標準樣式”和“混雜樣式”透過 document.compatMode和新的document.documentMode

上面不是說了一個文字節點作為第一子元素的坑嗎,所以瀏覽器又實現了一個children屬性,這個屬性只包含元素節點。

為了方便判斷A節點是不是B節點的子節點,引入了contains方法,比如

B.contains(A); // true就代表是,false就代表不是

這個方法有相容性問題,使用前可以谷歌解決方法。

針對訪問元素,又提供了4個方法innerText/innerHTML/outerTEXT/outerHTML。

透過這些方法,可以讀和寫元素。

其中,*TEXT是傳迴文字內容 *HTML是傳回html文字。

而outer*則是代表是否包含元素本身。

實際使用來看,在讀內容的時候 inner*和outer*沒有區別。

在把內容寫入元素的時候,就是是否包含元素本身的區別。

重要的是,這幾個方法有效能問題,比如在IE中,透過inner*刪除的節點,其系結的事件依然在記憶體中,就很容易消耗大量記憶體。

還有一個技巧是,插入大量的html程式碼,用innerHTML是非常快的,建議使用。

八、總結

首先感謝所有看到這裡的朋友,哈哈,關於DOM的東西實在是太多了,不過這也算是最重要的一個前端知識點之一吧。文章比較長,也許有點乏味,不過希望你們耐著性子看完後可以有所收貨^ ^。

我的筆記又更新了,這次主要更新了一個 移動端單頁宣傳頁開發方法 ,大家可以去看看^ ^。

九、MORE

DOM知識遠不止這些,接下來會簡單說說DOM的一些其餘介面

首先就是一個透過document查詢到視窗(也就是window)的方法

var parentWindow = document.defaultView || document.parentWindow;

defaultView是標準方法,parentWidow是相容IE用的。

然後,Node型別新增了 isSameNode()和isEqualNode()。

它們的區別就是:same是指同一個節點,equal是指“看過去”一模一樣的兩個節點。

提供了一個訪問iframe方法

標準的是透過ele.contentDocument 這個會傳回iframe裡面的document物件,但是有相容性問題,ie不支援,但是ie支援contentWindow,相當於拿到window物件,那麼document物件也很容易取到,所以一般這麼寫。

var iframe = document.getElementById(“myIframe”);

var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;

接下來說說處理樣式

設定樣式的基本的方式就是 ele.style.width = “20px”;這個大家估計很常用。 使用類似這種方法來處理要註意的點就是記得加單位。

快捷一點的方法就是 ele.style.cssText = “width: 20px;height:20px;”;這個可以批次設定。

上面說的是設定,那麼獲取元素所使用的樣式,一般而言會用 document.defaultView.getComputedStyle(ele, null);方法來實現。這個方法會傳回這個一個CSSStyleDeclaration物件(就和ele.style傳回的一樣),但是會包含這個元素的全部樣式資訊,然後直接傳回值.width或者別的屬性名就可以得到樣式了。

不過IE不支援這種方式,IE更簡單,直接ele.currentStyle就可以得到和 document.defaultView.getComputedStyle(ele, null)一樣的傳回值了。

最後說說元素的大小

取得元素的寬高 就使用 offsetHeight/offsetWidth,註意,不包括margin部分

得到元素的偏移值就複雜了一點,有兩個方法offsetTop/offsetLeft,但是這兩個方法是指元素針對與它的offsetParent物件而言的,所以,如果你想得到元素距離視口的位置,那麼還需要找到offsetParent,計算他們的offsetTop/offsetLeft,在找offsetParent的offsetParent,如此迴圈直到offsetParent為null。註意,這個計算的位置也不包括margin部分。

上面說的是不包括margin,然後還有一組屬性clientWidth/clientHeight,它們也可以得到寬高,但是它們是不包括margin和border,也不計算捲軸。

最後,滾動的位置與距離,這個很難說清,大家直接看圖比較清楚明瞭。

其中,scrollLeft和scrollTop是可以設定的,如果你把scrollTop設定成 “10px”,那麼元素就是滾動到10px的地方去。

來自:哎呦大黃

連結:http://www.cnblogs.com/season-huang/p/4322451.html


贊(0)

分享創造快樂