因為 DOM 的存在,這使我們可以通過 JavaScript 來獲取、創建、修改、或刪除節點。
NOTE:下面提供的例子中的 element 均為元素節點。
獲取節點
父子關系
element.parentNode element.firstChild/element.lastChild element.childNodes/element.children
兄弟關系
element.previousSibling/element.nextSibling element.previousElementSibling/element.nextElementSibling
通過節點直接的關系獲取節點會導致代碼維護性大大降低(節點之間的關系變化會直接影響到獲取節點),而通過接口則可以有效的解決此問題。
通過節點直接的關系獲取節點會導致代碼維護性大大降低(節點之間的關系變化會直接影響到獲取節點),而通過接口則可以有效的解決此問題。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>ELEMENT_NODE & TEXT_NODE</title> </head> <body> <ul id="ul"> <li>First</li> <li>Second</li> <li>Third</li> <li>Fourth</li> </ul> <p>Hello</p> <script type="text/javascript"> var ulNode = document.getElementsByTagName("ul")[0]; console.log(ulNode.parentNode); //<body></body> console.log(ulNode.previousElementSibling); //null console.log(ulNode.nextElementSibling); //<p>Hello</p> console.log(ulNode.firstElementChild); //<li>First</li> console.log(ulNode.lastElementChild); //<li>Fourth</li> </script> </body> </html>
NTOE:細心的人會發現,在節點遍歷的例子中,body、ul、li、p節點之間是沒有空格的,因為如果有空格,那麼空格就會被當做一個TEXT節點,從而用ulNode.previousSibling獲取到得就是一個空的文本節點,而不是 <li>First</li> 節點了。即節點遍歷的幾個屬性會得到所有的節點類型,而元素遍歷只會得到相對應的元素節點。一般情況下,用得比較多得還是元素節點的遍歷屬性。
實現浏覽器兼容版的element.children
有一些低版本的浏覽器並不支持 element.children 方法,但我們可以用下面的方式來實現兼容。
<html lang> <head> <meta charest="utf-8"> <title>Compatible Children Method</title> </head> <body id="body"> <div id="item"> <div>123</div> <p>ppp</p> <h1>h1</h1> </div> <script type="text/javascript"> function getElementChildren(e){ if(e.children){ return e.children; }else{ /* compatible other browse */ var i, len, children = []; var child = element.firstChild; if(child != element.lastChild){ while(child != null){ if(child.nodeType == 1){ children.push(child); } child = child.nextSibling; } }else{ children.push(child); } return children; } } /* Test method getElementChildren(e) */ var item = document.getElementById("item"); var children = getElementChildren(item); for(var i =0; i < children.length; i++){ alert(children[i]); } </script> </body> </html>
NOTE:此兼容方法為初稿,還未進行兼容性測試。
接口獲取元素節點
getElementById getElementsByTagName getElementsByClassName querySelector querySelectorAll
getElementById
獲取文檔中指定 id 的節點對象。
var element = document.getElementById('id'); getElementsByTagName
動態的獲取具有指定標簽元素節點的集合(其返回值會被 DOM 的變化所影響,其值會發生變化)。此接口可直接通過元素而獲取,不必直接作用於 document 之上。
// 示例 var collection = element.getElementsByTagName('tagName'); // 獲取指定元素的所有節點 var allNodes = document.getElementsByTagName('*'); // 獲取所有 p 元素的節點 var elements = document.getElementsByTagName('p'); // 取出第一個 p 元素 var p = elements[0];
getElementsByClassName
獲取指定元素中具有指定 class 的所有節點。多個 class 可的選擇可使用空格分隔,與順序無關。
var elements = element.getElementsByClassName('className');
NOTE:IE9 及一下版本不支持 getElementsByClassName
兼容方法
function getElementsByClassName(root, className) { // 特性偵測 if (root.getElementsByClassName) { // 優先使用 W3C 規范接口 return root.getElementsByClassName(className); } else { // 獲取所有後代節點 var elements = root.getElementsByTagName('*'); var result = []; var element = null; var classNameStr = null; var flag = null; className = className.split(' '); // 選擇包含 class 的元素 for (var i = 0, element; element = elements[i]; i++) { classNameStr = ' ' + element.getAttribute('class') + ' '; flag = true; for (var j = 0, name; name = className[j]; j++) { if (classNameStr.indexOf(' ' + name + ' ') === -1) { flag = false; break; } } if (flag) { result.push(element); } } return result; } }
querySelector / querySelectorAll
獲取一個 list (其返回結果不會被之後 DOM 的修改所影響,獲取後不會再變化)符合傳入的 CSS 選擇器的第一個元素或全部元素。
var listElementNode = element.querySelector('selector'); var listElementsNodes = element.querySelectorAll('selector'); var sampleSingleNode = element.querySelector('#className'); var sampleAllNodes = element.querySelectorAll('#className');
NOTE: IE9 一下不支持 querySelector 與 querySelectorAll
創建節點
創建節點 -> 設置屬性 -> 插入節點
var element = document.createElement('tagName');
修改節點
textContent
獲取或設置節點以及其後代節點的文本內容(對於節點中的所有文本內容)。
element.textContent; // 獲取 element.textContent = 'New Content';
NOTE:不支持 IE 9 及其一下版本。
innerText (不符合 W3C 規范)
獲取或設置節點以及節點後代的文本內容。其作用於 textContent 幾乎一致。
element.innerText;
NOTE:不符合 W3C 規范,不支持 FireFox 浏覽器。
FireFox 兼容方案
if (!('innerText' in document.body)) { HTMLElement.prototype.__defineGetter__('innerText', function(){ return this.textContent; }); HTMLElement.prototype.__defineSetter__('innerText', function(s) { return this.textContent = s; }); }
插入節點
appendChild
在指定的元素內追加一個元素節點。
var aChild = element.appendChild(aChild);
insertBefore
在指定元素的指定節點前插入指定的元素。
var aChild = element.insertBefore(aChild, referenceChild);
刪除節點
刪除指定的節點的子元素節點。
var child = element.removeChild(child);
innerHTML
獲取或設置指定節點之中所有的 HTML 內容。替換之前內部所有的內容並創建全新的一批節點(去除之前添加的事件和樣式)。innerHTML 不檢查內容,直接運行並替換原先的內容。
NOTE:只建議在創建全新的節點時使用。不可在用戶可控的情況下使用。
var elementsHTML = element.innerHTML;
存在的問題+
PS: appendChild() , insertBefore()插入節點需注意的問題
使用appendChild()和insertBefore()插入節點都會返回給插入的節點,
//由於這兩種方法操作的都是某個節點的子節點,所以必須現取得父節點,代碼中 someNode 表示父節點 //使用appendChild()方法插入節點 var returnedNode = someNode.appendChild(newNode); alert(returnedNode == newNode) //true //使用insertBefore()方法插入節點 var returnedNode = someNode.appendChild(newNode); alert(returnedNode == newNode) //true
值得注意的是,如果這兩種方法插入的節點原本已經存在與文檔樹中,那麼該節點將會被移動到新的位置,而不是被復制。
<div id="test"> <div>adscasdjk</div> <div id="a">adscasdjk</div> </div> <script type="text/javascript"> var t = document.getElementById("test"); var a = document.getElementById('a'); //var tt = a.cloneNode(true); t.appendChild(a); </script>
在這段代碼中,頁面輸出的結果和沒有Javascript時是一樣的,元素並沒有被復制,由於元素本來就在最後一個位置,所以就和沒有操作一樣。如果把id為test的元素的兩個子元素點換位置,就可以在firbug中看到這兩個div已經被調換了位置。
如果我們希望把id為a的元素復制一個,然後添加到文檔中,那麼必須使被復制的元素現脫離文檔流。這樣被添加復制的節點被添加到文檔中之後就不會影響到文檔流中原本的節點。即我們可以把復制的元素放到文檔的任何地方,而不影響被復制的元素。下面使用了cloneNode()方法,實現節點的深度復制,使用這種方法復制的節點會脫離文檔流。當然,我不建議使用這種方法復制具有id屬性的元素。因為在文檔中id值是唯一的。
<div id="test"> <div>adscasdjk</div> <div id="a">adscasdjk</div> </div> <script type="text/javascript"> var t = document.getElementById("test"); var a = document.getElementById('a'); var tt = a.cloneNode(true); t.appendChild(tt); </script>
相似的操作方法還有 removeNode(node)刪除一個節點,並返回該節;replaceNode(newNode,node)替換node節點,並返回該節點。這兩種方法相對來說更容易使用一些。