DOM是所有前端開(kāi)發(fā)每天打交道的東西寻拂,但是隨著jQuery等庫(kù)的出現(xiàn)委乌,大大簡(jiǎn)化了DOM操作返十,導(dǎo)致大家慢慢的“遺忘”了它的本來(lái)面貌。不過(guò)吸奴,要
想深入學(xué)習(xí)前端知識(shí),對(duì)DOM的了解是不可或缺的,所以本文力圖系統(tǒng)的講解下DOM的相關(guān)知識(shí)则奥,如有遺漏或錯(cuò)誤考润,還請(qǐng)大家指出一起討論^ ^。
一读处、DOM是什么糊治?
DOM(文檔對(duì)象模型)是針對(duì)HTML和XML文檔的一個(gè)API,通過(guò)DOM可以去改變文檔罚舱。
這個(gè)說(shuō)法很官方井辜,大家肯定還是不明白。
舉個(gè)例子:我們有一段HTML管闷,那么如何訪問(wèn)第二層第一個(gè)節(jié)點(diǎn)呢粥脚,如何把最后一個(gè)節(jié)點(diǎn)移動(dòng)到第一個(gè)節(jié)點(diǎn)上面去呢?
DOM就是定義了如果做類似操作包个,那么應(yīng)該怎么做的標(biāo)準(zhǔn)刷允。比如用getElementById來(lái)訪問(wèn)節(jié)點(diǎn),用insertBefore來(lái)插入節(jié)點(diǎn)碧囊。
當(dāng)瀏覽器載入HTML時(shí)树灶,會(huì)生成相應(yīng)的DOM樹(shù)。
簡(jiǎn)而言之糯而,DOM可以理解為一個(gè)訪問(wèn)或操作HTML各種標(biāo)簽的實(shí)現(xiàn)標(biāo)準(zhǔn)天通。
對(duì)于一個(gè)HTML來(lái)說(shuō),文檔節(jié)點(diǎn)Document(看不到的)是它的根節(jié)點(diǎn)熄驼,對(duì)應(yīng)的對(duì)象便是document對(duì)象(嚴(yán)格講是子類HTMLDocument對(duì)象像寒,下面單獨(dú)介紹Document類型時(shí)會(huì)指出)。
換句話說(shuō)存在一個(gè)文檔節(jié)點(diǎn)Document谜洽,然后它有子節(jié)點(diǎn)萝映,比如通過(guò)document.getElementsByTagName(“html”),得到類型為元素節(jié)點(diǎn)的Element html阐虚。
每一段HTML標(biāo)記都可以用相應(yīng)的節(jié)點(diǎn)表示序臂,例如:
HTML元素通過(guò)元素節(jié)點(diǎn)表示,注釋通過(guò)注釋節(jié)點(diǎn)表示实束,文檔類型通過(guò)文檔類型節(jié)點(diǎn)表示等奥秆。
一共定義了12種節(jié)點(diǎn)類型,而這些類型又都繼承自Node類型咸灿。
所以我們首先講Node類型构订,因?yàn)檫@個(gè)類型的方法是所有節(jié)點(diǎn)都會(huì)繼承的。
二避矢、Node類型(基類悼瘾,所有節(jié)點(diǎn)都繼承了它的方法)
Node是所有節(jié)點(diǎn)的基類型囊榜,所有節(jié)點(diǎn)都繼承自它,所以所有節(jié)點(diǎn)都有一些共同的方法和屬性亥宿。
先講Node類型的屬性
首先是nodeType屬性卸勺,用來(lái)表明節(jié)點(diǎn)類型的,例如:
document.nodeType;? ? // 返回 9 烫扼,其中document對(duì)象為文檔節(jié)點(diǎn)Document的實(shí)例
這里面曙求,9代表的就是DOCUMENT_NODE節(jié)點(diǎn)的意思,可以通過(guò)Node.DOCUMENT_NODE查看節(jié)點(diǎn)對(duì)應(yīng)的數(shù)字
document.nodeType === Node.DOCUMENT_NODE;? ? // true
至于一共有哪些節(jié)點(diǎn)映企,每個(gè)節(jié)點(diǎn)對(duì)應(yīng)的數(shù)字又是多少悟狱,這個(gè)可以問(wèn)谷歌就知道了。反正常用的就是元素節(jié)點(diǎn)Element(對(duì)應(yīng)數(shù)字為1)和文本節(jié)點(diǎn)Text(對(duì)應(yīng)數(shù)字為3)
然后常用的還有nodeName和nodeValue
對(duì)于元素節(jié)點(diǎn) nodeName就是標(biāo)簽名堰氓,nodeValue就是null
對(duì)于文本節(jié)點(diǎn) nodeName為”#text”(chrome里面測(cè)試的),nodeValue就是實(shí)際的值
每個(gè)節(jié)點(diǎn)還有childNodes屬性挤渐,這是個(gè)十分重要的屬性,它保存了這個(gè)節(jié)點(diǎn)所有直接子元素
調(diào)用childNodes返回的是一個(gè)NodeList對(duì)象豆赏,它極其像數(shù)組挣菲,但是有一個(gè)最關(guān)鍵的地方富稻,它是動(dòng)態(tài)查詢的掷邦,也就是說(shuō)每次調(diào)用它都會(huì)對(duì)DOM結(jié)構(gòu)查詢,所以對(duì)它的使用需要慎重椭赋,注意性能抚岗。
訪問(wèn)childNodes可以使用數(shù)組下表或者item方法
然后各個(gè)節(jié)點(diǎn)還存在各種屬性讓它們可以相互訪問(wèn),下圖很好的總結(jié)了
比較有用的方法和屬性:
還有一點(diǎn)哪怔,如果要?jiǎng)討B(tài)寫入腳本 例如 xxx這樣的
宣蔚,那么要注意把分開(kāi)來(lái)拼裝下,否則會(huì)被誤以為是腳本結(jié)束的標(biāo)志认境,導(dǎo)致這個(gè)結(jié)束符匹配到上面一個(gè)開(kāi)始符胚委。可以這樣
寫””;
四叉信、Element類型
接下來(lái)講講最重要也是最常見(jiàn)的一個(gè)類型亩冬,Element類型。
我們?nèi)粘K僮鞯亩际荅lement類型(實(shí)質(zhì)是HTMLElement硼身,這里為了方便理解硅急,就簡(jiǎn)單這么說(shuō)),比如
document.getElementById("test")
返回的就是Element類型佳遂。我們?nèi)粘Kf(shuō)的“DOM對(duì)象”营袜,通常也就是指Element類型的對(duì)象。
然后說(shuō)說(shuō)這個(gè)類型的常見(jiàn)屬性:
首先最開(kāi)始說(shuō)的Node類型上的那些屬性方法它都有丑罪,這個(gè)就不再重復(fù)了荚板,主要說(shuō)說(shuō)它自己獨(dú)有的凤壁。
首先是tagName,這個(gè)和繼承自Node類型的nodeName一樣跪另。都是返回標(biāo)簽名客扎,通常是大寫,結(jié)果取決于瀏覽器罚斗。所以在做比較
的時(shí)候最好是調(diào)用下類似toLowerCase()這種方法再做比較徙鱼。
說(shuō)說(shuō)上面提到過(guò)的HTMLElement類型
HTMLElement類型繼承自Element類型,也是HTML元素的實(shí)際類型针姿,我們?cè)跒g覽器里用的元素都是這個(gè)類型袱吆。
這個(gè)類型都具有一些標(biāo)準(zhǔn)屬性,比如:
id 元素的唯一標(biāo)識(shí)
title 通常是鼠標(biāo)移上去時(shí)候會(huì)顯示的信息
className 類名
等等距淫,這幾個(gè)屬性是可讀寫的绞绒,也就是說(shuō)你改變他們會(huì)得到相應(yīng)的效果。
除了屬性外榕暇,還有幾個(gè)重要的方法
首先說(shuō)說(shuō)操作節(jié)點(diǎn)屬性的方法
getAttribute 蓬衡、setAttribute 、removeAttribute這3個(gè)方法彤枢。
這些是操作屬性最常用的方法了狰晚,怎么用就不說(shuō)了,很簡(jiǎn)單缴啡,顧名思義壁晒。
還有一個(gè)attributes屬性,保存了元素的全部屬性业栅。
這里停下來(lái)秒咐,出個(gè)問(wèn)題,ele.className 和 ele.getAttribute(“class”)返回的結(jié)果是不是同一個(gè)東西碘裕?
解答這個(gè)問(wèn)題携取,我要說(shuō)一個(gè)重要知識(shí)點(diǎn),一個(gè)元素的屬性結(jié)構(gòu)是這么來(lái)的帮孔,比如一個(gè)inpnt元素
那么這個(gè)元素的屬性被包含在 input.attributes里面雷滋,比如你在html元素上看到的class、id或者你自己定義的data-test這種屬性你弦。
然后?getAttribute 惊豺、setAttribute 、removeAttribute這3個(gè)方法可以認(rèn)為是快捷的取attributes集合的方法禽作。而直接input.id或者input.className都是直接掛在input下的屬性尸昧,和attributes是同級(jí)的。所以返回的東西也許看過(guò)去一樣旷偿,實(shí)際是不一樣的烹俗,不信你可以試試input.checked這input.getAttribute(“checked”)試試爆侣。
關(guān)于這個(gè)知識(shí)點(diǎn),詳細(xì)的說(shuō)可以再寫一篇文章幢妄,在我的博客從is(“:checked”)說(shuō)起中有談到過(guò)兔仰,大家可以看看這篇文章和文章后的討論,便可以知道是怎么一回事蕉鸳。
總得來(lái)說(shuō)乎赴,這3個(gè)方法通常用了處理自定義的屬性,而不是id潮尝、class等這種“公認(rèn)特性”榕吼。
接下來(lái)說(shuō)說(shuō)創(chuàng)建元素
document.createElement()可以創(chuàng)建一個(gè)元素,比如:
document.createElement("div");
一般之后可以為元素設(shè)置屬性勉失,兩種方法羹蚣,一種是直接node.property還可以node.setAttribute(“propertyName”,”value”)。等
但是做完這些之后乱凿,這個(gè)元素還是沒(méi)有在頁(yè)面中顽素,所以你還得通過(guò)最上面講的類似appendChild這些方法把元素添加到頁(yè)面里面。
在IE中徒蟆,還可以直接穿整個(gè)HTML字符串進(jìn)去胁出,來(lái)創(chuàng)建元素,比如
document.createElement("
test
");
最后后专,元素節(jié)點(diǎn)也支持HTMLDocument類型的那些查找方法划鸽,比如getElementsByTagName。不過(guò)它只會(huì)找自己后代的節(jié)點(diǎn)戚哎。所以可以這么寫代碼
document.getElementById("test").getElementsByTagName("div");? ? // 找到id為test元素下的所有div節(jié)點(diǎn)
五、Text類型
這個(gè)類型很特殊嫂用,也是第三常見(jiàn)類型(第一第二分別就是Document和Element)型凳。
這個(gè)節(jié)點(diǎn)簡(jiǎn)單來(lái)說(shuō)就是一段字符串。
有個(gè)很重要的特征就是嘱函,它沒(méi)有子元素(不過(guò)這個(gè)仔細(xì)想想也知道= =)
訪問(wèn)text節(jié)點(diǎn)的文本內(nèi)容甘畅,可以通過(guò)nodeValue或者data屬性。
下面簡(jiǎn)單說(shuō)說(shuō)它提供的一些方法
appendData();? ? // 在text末尾加內(nèi)容
deleteData(offset, count);? ? // 從offset指定的位置開(kāi)始刪除count個(gè)字符
還有insertDate往弓、replaceData疏唾、splitText等方法,就不一一說(shuō)了函似,用的機(jī)會(huì)很少槐脏,可以用的時(shí)候再查閱。
然后它還有一個(gè)lenght屬性撇寞,返回字符長(zhǎng)度的顿天。
這里說(shuō)一個(gè)常見(jiàn)的坑堂氯。比如下面這個(gè)html結(jié)構(gòu)
這里,ul的第一個(gè)子節(jié)點(diǎn)(firstChild)是什么呢牌废?第一眼看過(guò)去咽白,肯定認(rèn)為是li了,但是實(shí)際上鸟缕,你會(huì)發(fā)現(xiàn)不是li晶框,而是一個(gè)文本節(jié)點(diǎn)!
這是因?yàn)闉g覽器認(rèn)為ul和第一個(gè)li之間有空白字符懂从,所以就有文本節(jié)點(diǎn)了三妈。
這里一個(gè)常見(jiàn)的問(wèn)題就是遍歷ul的childNodes的時(shí)候,遍歷的元素一定要判斷下nodeType是不是等于1(等于1就代表是元素節(jié)點(diǎn))莫绣,這樣才能跳過(guò)這個(gè)坑畴蒲。否則你也可以刪除所有的空格和換行符。
創(chuàng)建文本節(jié)點(diǎn)的方法是document.createTextNode
然后接下來(lái)和操作Element類型一樣对室,就是再插入到元素中模燥,瀏覽器就可以看到了。
六掩宜、其他的一些類型 Comment蔫骂、DocumentType和DocumentFragment
這些不常用的一句話帶過(guò)把
Comment是注釋節(jié)點(diǎn)
DocumentType就是doctype節(jié)點(diǎn),通過(guò)docment.doctype來(lái)訪問(wèn)
DocumentFragment這個(gè)節(jié)點(diǎn)是一個(gè)文檔片段牺汤,偶爾會(huì)用到辽旋。
比如一種常見(jiàn)的用法是,在一個(gè)ul中插入3個(gè)li檐迟。
如果你循環(huán)插入3次补胚,那么瀏覽器就要渲染3次,對(duì)性能有蠻大的影響追迟。
所以大家一般這么做
先
var fragment = document.createDocumentFragment();
然后循環(huán)把li溶其,用appendChild插入到fragment里面
最后在一次把fragment插入到ul里面。這樣就會(huì)很快敦间。
七瓶逃、DOM擴(kuò)展
進(jìn)過(guò)上面講的這么多節(jié)點(diǎn)類型,想必大家對(duì)DOM節(jié)點(diǎn)已經(jīng)有了很深的了解廓块,下面講一講DOM擴(kuò)展的一些東西厢绝。
瀏覽器為了方便開(kāi)發(fā)者,擴(kuò)展了一些DOM功能带猴。
因?yàn)槭菫g覽器自己擴(kuò)展的昔汉,所以使用前兼容性問(wèn)題一定要注意
判斷“標(biāo)準(zhǔn)模式”和“混雜模式”通過(guò) document.compatMode和新的document.documentMode
上面不是說(shuō)了一個(gè)文本節(jié)點(diǎn)作為第一子元素的坑嗎,所以瀏覽器又實(shí)現(xiàn)了一個(gè)children屬性浓利,這個(gè)屬性只包含元素節(jié)點(diǎn)挤庇。
為了方便判斷A節(jié)點(diǎn)是不是B節(jié)點(diǎn)的子節(jié)點(diǎn)钞速,引入了contains方法,比如
B.contains(A);? ? // true就代表是嫡秕,false就代表不是
這個(gè)方法有兼容性問(wèn)題渴语,使用前可以谷歌解決方法。
針對(duì)訪問(wèn)元素昆咽,又提供了4個(gè)方法innerText/innerHTML/outerTEXT/outerHTML驾凶。
通過(guò)這些方法,可以讀和寫元素掷酗。
其中调违,*TEXT是返回文本內(nèi)容?*HTML是返回html文本。
而outer*則是代表是否包含元素本身泻轰。
實(shí)際使用來(lái)看技肩,在讀內(nèi)容的時(shí)候 inner*和outer*沒(méi)有區(qū)別。
在把內(nèi)容寫入元素的時(shí)候浮声,就是是否包含元素本身的區(qū)別虚婿。
重要的是,這幾個(gè)方法有性能問(wèn)題泳挥,比如在IE中然痊,通過(guò)inner*刪除的節(jié)點(diǎn),其綁定的事件依然在內(nèi)存中屉符,就很容易消耗大量?jī)?nèi)存剧浸。
還有一個(gè)技巧是,插入大量的html代碼矗钟,用innerHTML是非乘粝悖快的,建議使用真仲。
八袋马、總結(jié)
首先感謝所有看到這里的朋友,哈哈秸应,關(guān)于DOM的東西實(shí)在是太多了,不過(guò)這也算是最重要的一個(gè)前端知識(shí)點(diǎn)之一吧碑宴。文章比較長(zhǎng)软啼,也許有點(diǎn)乏味,不過(guò)希望你們耐著性子看完后可以有所收貨^ ^延柠。