操作DOM
由於HTML文檔被瀏覽器解析後就是一顆DOM樹,要改變HTML的結構,就需要通過JavaScript來操作DOM。
始終記住DOM是一個樹形結構。操作一個DOM節點實際上就是這麽幾個操作:
更新:更新該DOM節點的內容,相當於更新了該DOM節點表示的HTML的內容。
遍歷:遍歷該DOM節點下的子節點。以便於進一步操作;
添加:在該DOM節點下新增一個子節點,相當於動態增加了一個HTML節點;
刪除:將該節點從HTML中刪除。相當於刪除掉了該DOM節點的內容以及它包含的所有子節點。
在操作DOM節點前,我們需要通過各種方式先拿到這個DOM節點。最常用的方法是document.getElementById()和document.getElementsByTagName(),以及CSS選擇器document.getElementsByClassName();
由於ID在HTML文檔中是唯一的,所以document.getElementById()可以直接定位唯一的一個DOM節點。document.getElementsByTagName()和document.getElementsByClassName()總是返回一組DOM節點。要精確地選擇DOM,可以先定位父節點,再從父節點開始選擇,以縮小範圍。
//返回ID為’test‘的節點 var test = document.getElementById(‘test‘); //先定位ID為’test-table‘的節點,再返回其內部所有tr節點 vartrs = document.getElementById(‘test-table‘).getElementsByTagName(‘tr‘); //先定位ID為’test-div‘的節點,再返回其內部所有class包含red的節點。 var reds = document.getElementById(‘test-div‘).getElementsByClassName(‘red‘); //獲取節點test下的所有直屬子節點: var cs = test.children; //獲取節點test下第一個、最後一個子節點:var first = test.firstElementChild; var last = test.lastElementChild;
第二種方法是使用querySelector()和querySelectorAll(),需要了解selector語法,然後使用條件來獲取節點,更加方便:
//通過querySelector獲取ID為q1的節點: var q1 = document.querySelector(‘#q1‘); //通過querySelectorAll獲取q1節點內的class為highlighted的div的所有子元素p: var ps = q1.querySelectorAll(‘div.highlighted >p‘);
低版本的IE<8不支持querySelectorAll和querySelector,IE8有的支持。
嚴格來講,我們這裏的DOM節點是指Element,但是DOM節點實際上是Node,在HTML中,Node包括Element、Comment、CDATA_SECTION等很多種,以及根節點Document類型,但是,絕大多數時候我們只關心Element,也就是實際控制頁面結構的Node,其他類型的Node忽略即可。根節點Document已自動綁定為全局變量document
更新DOM
拿到一個DOM節點後,我們可以對它進行更新。
可以直接修改節點的文本,方法有兩種
一是修改innerHTML屬性,這個方式非常強大,不但可以修改一個DOM節點的文本內容,還可以直接通過HTML片段修改DOM節點內部的子樹。
//獲取<p id="p-id">...</p> var p = document.getElementById("p-id"); //設置文本為abc p.innerHTML=‘ABC‘;//<p id="p-id">ABC</p> //設置HTML: p.innerHTML=‘ABC<span style="color:red">RED</span>XYZ‘;//<p>...</p>的內部結構已修改。
用innerHTML時要註意,是否需要寫入HTML。如果寫入的字符串是通過網絡拿到,要註意對字符編碼來比嗎XSS共計。
第二種是修改innerText或者textContent屬性,這樣可以自動對字符串進行HTML編碼,保證無法設置任何HTML標簽:
// 獲取<p id="p-id">...</p> var p = document.getElementById(‘p-id‘); // 設置文本: p.innerText = ‘<script>alert("Hi")</script>‘; // HTML被自動編碼,無法設置一個<script>節點: // <p id="p-id"><script>alert("Hi")</script></p>
兩者的區別在於讀取屬性時,innerText不返回隱藏元素的文本,而textContent返回所有文本。另外註意IE<9不支持textConten
修改CSS也是經常需要的操作。DOM節點的style屬性對應所有的CSS,可以直接獲取或者設置。因為css允許font-size這樣的名稱,但它並非JavaScript有效的屬性名,所以需要在JavaScript中改寫為駝峰式命名fontSize:
// 獲取<p id="p-id">...</p> var p = document.getElementById(‘p-id‘); // 設置CSS: p.style.color = ‘#ff0000‘; p.style.fontSize = ‘20px‘; p.style.paddingTop = ‘2em‘;
插入DOM
當我們獲得了某個DOM節點,想在這個DOM節點內插入新的DOM,應該如何做?
如果這個DOM節點是空的,例如,<div></div>,那麽,直接使用innerHTML=‘<span>child</span>’就可以修改DOM節點的內容,相當於“插入”了新的DOM節點。
如果這個DOM節點不是空的,那就不能這麽做,因為innerHTML會直接替換掉原來的所有子節點。
有兩個辦法可以插入新的節點,一個是使用appendChild,把一個子節點添加到父節點的最後一個子節點。
HTML結構:
<p id="js">JavaScript</p> <div id="list"> <p id="java">Java</p> <p id="python">python</p> <p id="scheme">scheme</p> </div>
把<p id="js">javascript</p>添加到<div id="list">的最後一項:
var js = document.getElementById(‘js‘), list = document.getElementById(‘list‘); list.appendChild(js);
現在html結構變成這樣:
<div id="list"> <p id="java">Java</p> <p id="python">python</p> <p id="scheme">scheme</p>
<p id="js">JavaScript</p> </div>
因為我們插入的js節點已經存在於當前的文檔樹,因此這個節點首先會從原來的位置刪除,再插入到新的位置。
更多的時候我們會從零創建一個新的節點,然後插入到指定位置:
var list = document.getElementById(‘list‘), haskell = document.createElement(‘p‘); haskell.id = ‘haskell‘; haskell.innerText = ‘Haskell‘; list.appendChild(haskell);
這樣我們就動態添加了一個新的節點:
<div id="list"> <p id="java">Java</p> <p id="python">python</p> <p id="scheme">scheme</p> <p id="haskell">Haskell</p> </div>
動態創建一個節點然後添加到DOM樹種,可以實現很多功能,舉個例子,下面的代碼動態地創建了一個<style>節點,然後把它添加到<head>節點的末尾,這樣就動態地給文檔添加了新的css定義:
var d = documnet.createElement(‘style‘); d.setAttribute(‘type‘,‘text/css‘); d.innerHTML=‘p{color:red}‘; document.getElementsByTagName(‘head‘)[0].appendChild(d);
inertBefore
如果我們要把子節點插入到指定的位置怎麽辦?可以使用parentElement.insertBefore(newElement, referenceElement);
子節點會插入到referenceElement之前。
假如我們要把Haskell插入到python之前:
var list = document.getElementsById(‘list‘), ref = document.getElementById(‘python‘), haskell = document.createElement(‘p‘); haskell.id =‘haskell‘; haskell.innerText = ‘Haskell‘; list.insertBefore(haskell,ref);
新的HTML結構:
<div id="list"> <p id="java">Java</p> <p id="haskell">Haskell</p> <p id="python">Python</p> <p id="scheme">Scheme</p> </div>
使用insetBefore重點是要拿到一個“參考子節點”的引用。很多時候,需要循環一個父節點的所有子節點,可以通過叠代children屬性實現:
var i,c,list = document.getElementById(‘list‘); for(i=0;i<list.children.length;i++){ c=list.children[i];//拿到第i個子節點 }
刪除DOM
刪除一個DOM節點就比插入要容易多。
要刪除一個節點,首先要獲得該節點本身以及它的父節點,然後,調用父節點的removeChild把自己刪掉
//拿到待刪除的節點 var self = document.getElementById(‘to-be-removed‘); //拿到父節點 var parent = self.parentElement; //刪除 var removed = parent.removeChile(self); removed === self;//true
註意到刪除後的節點雖然不在文檔樹中了,但其實它還在內存中,可以隨時再次被添加到別的位置。
當你遍歷一個父節點的子節點並進行刪除操作時,要註意,children屬性是一個只讀屬性,並且它的子節點變化時會實時更新
例如對於以下HTML
<div id="parent"> <p>First</p> <p>Second</p> </div>
當我們用如下代碼刪除子節點時:
var parent = document.getElementById(‘parent‘); parent.removeChild(parent.children[0]); parent.removeChild(parent.children[1]); // <-- 瀏覽器報錯
瀏覽器報錯:parent.children[1]不是一個有效的節點。原因就在於,當<p>First</p>節點被刪除後,parent.children的節點數量已經從2變為了1,索引[1]已經不存在了。
因此,刪除多個節點時,要註意children屬性時刻都在變化
操作DOM