跳到主要內容

JavaScript DOM 操作

當瀏覽器讀取完網頁後,便會在JavaScript中生成一個全域變數 document
這個document物件就是這次討論的主題 Document Object Model(DOM)
透過它,我們能將HTML網頁結構視為XML做動態的處理

DOM將網頁視為節點構成的階層樹,如下圖所示:

階層樹的頂點是Document,並往下聯繫到各個element形成的節點
網頁element的文字內容則儲存在element下的文字節點(Text node)
Attribute是element的屬性,可以透過Element node取得,但不會直接出現在階層樹裡

document 提供了 getElementById()、getElementsByTagName()等方法方便讓你取得element進行操作

getElementById() 可以從整份DOCUMENT中取得有指定id的element
需要注意的是在HTML的標準規範中,id在每個DOCUMENT中只能是獨特的
所以getElementById() 這個method回傳的是單一的element
即使你對複數個element賦予了相同的id值,它也只會回傳第一個符合的element
這點可以從method的命名get"Element"ById看出
其他的get"Elements"By... 之類的則是回傳element 集合,要取得其中特定的element可以用陣列的方式
如:getElementsByTagName("div")[0]  取得第一個div element

大概是JQuery的成功,現在的瀏覽器也加入了 querySelector()、querySelectorAll()  兩個method的實作
讓你可以像使用JQuery一樣,使用各式各樣的選取器取得單一 element 或 element 集合
如果你的操作環境不需要考慮舊瀏覽器的相容性,使用這些較新的API應該會比較方便

對於新人來說,有一個觀念需要澄清
很多新人會在HTML 的 head tag 裡加入script tag並於其中撰寫JavaScript
這當然是正確的觀念,但有問題的是後續處理
此時若直接使用getElementById等API取回來的會是null
原理也很單純,JavaScript是一行一行執行的直譯式程式語言
要完整的讀取完整個網頁才會建立完整的DOM
所以若在瀏覽器讀到head時就要執行getElementById(),則會因為該element尚不存於DOM中而無法操作
解決方式就是在DOM建立完成後執行該method就可以了,有window.onload等事件可以協助處理

在各個節點可以取得我們需要的特性:
nodeName - 回傳節點的標籤名稱,如<div>會回傳 DIV
nodeValue - 儲存於節點的值,只限文字與屬性節點使用
nodeType - 回傳節點型態
                   element node回傳1,attribute node回傳2
                   text node回傳3,包括換行符號等
childNodes - 包含節點下所有子節點的element集合,以出現在HTML碼裡的順序排列
childElementCount - 回傳 element 子代 (child) element的數量
parentNode - 回傳其父節點
firstChild - 節點下的第一個子節點
lastChild - 節點下的最後一個子節點
nextSibling - 下一個節點(節點範圍不限同一層)
previousSibling - 上一個節點(節點範圍不限同一層)

較特殊的:
document.baseURI - 取得目前文件的路徑(即目前網址)
style - 設定或取得某個節點的屬性,類似CSS設定,如:node.style.width='100px';
className - 設定或取得節點的class屬性
firstElementChild - 取得節點下的第一個element node,在現代的新瀏覽器多半有實作
                             另有類似的lastElementChild、nextElementSibling、previousElementSibling

另外說明.children屬性
這個也是相當常被看到的屬性,使用的方式也很類似childNodes
差別在於
1.childNodes是W3C標準,children則不是(但基本上browser都會實作這個屬性)
2.childNodes會回傳 NodeList,children則是HTMLCollection
3.children回傳的內容不包括 text node等,僅回傳 element node
   所以如有下列的情形
   <div>
         <a></a>
   </div>

   像這樣節點間存在換行符號、空白
   用 childNodes.firstChild 便有可能取不到我們真正想要的 node(視 browser 實作情形)

可以在節點上使用的方法則有
appendChild() - 加入子節點
cloneNode() -  參數設定為 true 可複製該element的所有子代 (child)
                        設定為 false 則通常只會複製一個文字節點
    範例:
    var demo = document.getElementById("demo");
    var demo2 = demo.cloneNode(true);
    document.body.appendChild(demo2);

hasChildNodes() - 回傳boolean值,判斷有無子節點
hasAttributes() - 回傳是否有無屬性值
insertBefore() - 使用方式:parentNode.insertBefore(newElement, referenceElement)
removeChild() - 刪除子節點,使用方式:parentNode.removeChild(node)
replaceChild() - 使用方式:node.replaceChild(newChild, oldChild);
getAttribute() - 取得節點的某項屬性,如:node.getAttribute("nowrap")
setAttribute() - 設定節點的某項屬性,如:node.getAttribute("height", "100px")
removeAttribute() - 刪除節點的某項屬性,如:node.getAttribute("nowrap")


使用範例:document.getElementsByTagName("div")[0].childNodes[1].nodeValue = 1;
當然範例要先確定childNode[1]底下只有一個節點,否則會得不要想要的效果
如果不確定所要更改的節點內容是否有多個子節點
那麼更改的時候就要先清除所有的子節點後,再新增有新內容的新子節點,如:
var node = document.getElementById("target");
//有子節點的話,firstChild回傳true
while(node.firstChild){ node.removeChild(node.firstChild); }
node.appendChild(document.createTextNode("alter OK"));

當然先寫好一個替換節點文字內容的函式,通常是個方便的選擇,如:
function replaceNodeText(id, newText){
   var node = document.getElementById(id);
   while(node.firstChild){ node.removeChild(node.firstChild); } 
   node.appendChild(document.createTextNode(newText));
}

DOM提供了透過節點調整CSS的機制
透過節點物件的className特性可以操作element的類別(class)特性
如:document.getElementById("target").className = "deco2";
節點的style特性提供對單一樣式性質的存取
如:document.getElementById("target").style.visibility = "hidden";

前面看到過的createTextNode()只能建立純粹的文字內容
接著要提的createElement() 可以用來建立任何的HTML元件
建立段落文字的範例:
var newElem = document.createElement("p");
newElem.appendChild(document.createTextNode("Hello World"));
document.getElementById("target").appendChild(newElem); 

加入的段落文字會以符合標準的形式呈現 -> <p>Hello World</p>

較罕見的功能有createHTMLDocument(),可用來建立一個看不見的document
var doc = document.implementation.createHTMLDocument(title);

留言