DOM禀忆,文檔對象模型,是一個針對HTML和XML文檔的一個API呀潭,它描繪了一個層次化的節(jié)點樹械蹋。值得注意的是IE中的所有DOM對象都是使用COM對象實現(xiàn)的,這就造成了IE中的DOM對象與原生JS對象表現(xiàn)并不一致绸硕。
節(jié)點層次
文檔節(jié)點是每個文檔的根節(jié)點堂竟。在html文檔中,文檔節(jié)點只有一個子節(jié)點即html元素玻佩。這個元素我們稱之為文檔元素出嘹,每個文檔只有一個文檔元素,其他所有元素都包含在文檔元素中咬崔。
節(jié)點分為幾種不同的類型税稼,每種類型分別表示文檔中不同的信息及標記。
比如:元素節(jié)點垮斯,特性節(jié)點郎仆,文檔節(jié)點,注釋節(jié)點等兜蠕∪偶。總共有12種節(jié)點類型,這些類型都繼承自一個基類型熊杨。
Node類型
這是DOM1級定義的一個接口曙旭,DOM中所有類型節(jié)點都會實現(xiàn)這個接口。在JS中作為Node類型實現(xiàn)晶府,所有節(jié)點類型皆繼承自Node類型夷狰。不過IE直接訪問不到Node。Node類型由下面12個常量表示:
- Node.ELEMENT_NODE(1)
- Node.ATTRIBUTE_NODE(2)
- Node.TEXT_NODE(3)
- Node.CDATA_SECTION_NODE(4)
- Node.ENTITY_REFERENCE_NODE(5)
- Node.ENTITY_NODE(6)
- Node.PROCESSING_INSTRUCTION_NODE(7)
- Node.COMMENT_NODE(8)
- Node.DOCUMENT_NODE(9)
- Node.DOCUMENT_TYPE_NODE(10)
- Node.DOCUMENT_FRAGMENT_NODE(11)
- Node.NOTATION_NODE(12)
var htmlNode = document.getElementsByTagName("html")[0];
//本來標準的寫法應(yīng)該是與Node.ELEMENT_NODE這個常量做比較郊霎,但是由于IE訪問不到Node這個類型沼头,所以只好直接比較
if (htmlNode.nodeType == 1){
alert("Node is an element.");
alert(htmlNode.nodeName); //HTML
alert(htmlNode.nodeValue); //null
}
這里查找html元素時使用的方法返回的是一個NodeList對象,基本對node類型的查找和獲取都會涉及到這個對象书劝。這個對象是一個類數(shù)組對象进倍,許多使用方法都和數(shù)組是一樣的,但是它并不是Array類型的實例购对。其特別的地方在于它其實是基于DOM結(jié)構(gòu)動態(tài)執(zhí)行查詢的結(jié)果猾昆,DOM結(jié)構(gòu)的變化可以自動反應(yīng)在NodeList中。想將NodeList轉(zhuǎn)換為數(shù)組骡苞,可以使用Array.prototype.slice()方法垂蜗。
var bodyNode = document.getElementsByTagName("body")[0];
var childList = bodyNode.childNodes;
var childListArray = Array.prototype.slice.call(childList,0);
alert(childList.length); //8
alert(childListArray.length); //8
bodyNode.removeChild(bodyNode.lastChild);
alert(childList.length); //7
alert(childListArray.length); //8
節(jié)點關(guān)系
就是父元素與子元素咯楷扬。
bodyNode.childNodes;
bodyNode.parentNode;
bodyNode.previousSibling;
bodyNode.nextSibling;
bodyNode.firstChild;
bodyNode.lastChild;
bodyNode.hasChildNodes();
bodyNode.ownerDocument;
不是每種節(jié)點都有子節(jié)點哦,有的不能有子節(jié)點的贴见。
操作節(jié)點
appendChild()用于向childNodes列表結(jié)尾添加一個節(jié)點烘苹。如果傳入到這個方法中的節(jié)點已經(jīng)是文檔的一部分了,那么該節(jié)點就會被移動到新節(jié)點片部。任何DOM節(jié)點不能同時出現(xiàn)在多個位置镣衡。
//someNode 有多個節(jié)點
var returnedNode = someNode.appendChild(someNode.firstChild);
alert(returnedNode == someNode.firstChild); //false
alert(returnedNode == someNode.lastChild); //true
insertBefore()
var returnedNode = someNode.insertBefore(newNode, someNode.firstChild);
alert(returnedNode == newNode); //true
alert(newNode == someNode.firstChild); //true
replaceChild()
在替換的過程中,該節(jié)點的所有關(guān)系指針都會從被它替換的節(jié)點復(fù)制過來档悠,被替換的節(jié)點還在文檔中廊鸥,但是失去了自己的位置。
var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
removeChild()
同樣的辖所,被移除的節(jié)點還是歸文檔所有惰说,沒有位置了。
var formerFirstChild = someNode.removeChild(someNode.firstChild);
cloneNode()
這個方法接受一個布爾值表示是否進行深復(fù)制缘回,深復(fù)制就是復(fù)制節(jié)點及其子節(jié)點樹助被。要注意的是,復(fù)制后的節(jié)點沒有父節(jié)點切诀,是孤獨的節(jié)點揩环。。需要使用上面提到的方法添加幅虑。
normalize()
這個方法是處理文檔樹中的文本節(jié)點丰滑。
Document類型
document是window的一個屬性,是Document的實例倒庵。
- nodeType:9
- nodeName:"#document"
- nodeValue:null
- parentNode:null
- ownerDocument:null
- 其子節(jié)點可以是:DocumentType(最多一個)褒墨、Element(最多一個)、ProcessingInstruction擎宝、Comment
文檔的子節(jié)點
document.documentElement//指向html元素
document.body//指向body元素
document.doctype;//指向<!DOCTYPE>
文檔信息
document.title = "New page title";
document.URL;
document.domain;
document.referrer;//來自哪個頁面的URL
其中只有title和domain是可寫的郁妈。
domain對寫入的值是有限制的,只能寫入當前域的父域绍申。
//來自p2p.wrox.com
document.domain = "wrox.com"; //成功
document.domain = "nczonline.net"; //報錯
document.domain = "p2p.wrox.com "; //報錯
對于包含來自其他子域框架的頁面這個很有用噩咪,因為來自不同域的頁面不能通過JavaScript通信,所以通過將domain設(shè)置為同一個父域极阅,就可以實現(xiàn)互相訪問對方的JS對象胃碾。
查找元素
//這個返回一個元素
document.getElementById("myDiv");
//這個返回元素列表HTMLCollectioin
document.getElementsByTagName("img");
//找到name屬性符合的元素。這個返回元素列表HTMLCollectioin
document.getElementsByName("color");
var ul = document.getElementById("myList");
//可以直接在元素里查找
var items = ul.getElementsByTagName("li");
一些特殊集合:
document.anchors
document.applets
document.forms
document.images
document.links
DOM一致性檢測
用來檢測瀏覽器實現(xiàn)了DOM的哪些部分筋搏。DOM1提供了一個方法仆百。將要檢測的功能和版本號傳入。
var hasXmlDom = document.implementation.hasFeature("XML", "1.0");
不過這個方法并不保險奔脐,有的瀏覽器對沒實現(xiàn)的功能也返回true俄周,所以建議對不確定的直接進行能力檢測吁讨。
文檔寫入
將輸出流寫入到網(wǎng)頁中的功能。有4個方法:write()峦朗、writeln()建丧、open()、close()甚垦。
<html>
<head>
<title>document.write() Example 3</title>
</head>
<body>
<script type="text/javascript">
document.write("<strong>" + (new Date()).toString() + "</strong>");
</script>
</body>
</html>
在文檔加載過程中使用茶鹃,則會加載內(nèi)容到當前位置涣雕,在文檔加載結(jié)束后再執(zhí)行則會覆蓋掉整個文檔
Element類型
Element就是我們最常用的元素
- nodeType:1
- nodeName:元素的標簽名
- nodeValue:null
- parentNode:Document或Element
- ownerDocument:Document
- 其子節(jié)點可以是:Element艰亮、Text、Comment挣郭、ProcessingInstruction迄埃、CDATASection、EntityReference
HTML元素
所有HTML元素都是HTMLElement或其子類兑障。HTMLElement直接繼承自Element侄非。有這么幾個通用屬性:
//<div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>
var div = document.getElementById("myDiv");
alert(div.id); //"myDiv""
alert(div.className); //"bd"
alert(div.title); //"Body text"
alert(div.lang); //"en"
alert(div.dir); //"ltr"
取得特性
getAttribute()、setAttribute()流译、removeAttribute()
alert(div.getAttribute("id"));
alert(div.getAttribute("class"));
alert(div.getAttribute("title"));
alert(div.getAttribute("lang"));
alert(div.getAttribute("data_myOwnAttr"));
alert(div.data_myOwnAttr); //undefined(除IE)
要注意的是逞怨,只有公認的特性才能通過屬性名的方式來訪問,自定義的特性除IE外的瀏覽器不會為他們創(chuàng)建屬性福澡。
對于style這個屬性叠赦,直接訪問屬性得到的是一個對象,便于訪問各個樣式革砸;而getAttribute("style")則返回字符串除秀。
對于onclick這樣的事件處理特性,直接訪問屬性得到的是一個函數(shù)算利,getAttribute()則返回字符串册踩。
所以在訪問非自定義屬性時推薦使用直接訪問元素屬性的方法。
設(shè)置特性
div.setAttribute("id", "someOtherId");
div.id = "someOtherId";
同樣效拭,通過屬性來設(shè)置自定義特性是無效的暂吉。
刪除特性
div.removeAttribute("class");
attributes屬性
只有Element類型有這個屬性。這個屬性中包含一個NameNodeMap缎患。元素的每一個特性由一個Attr節(jié)點表示借笙,每個節(jié)點都保存在NameNodeMap對象中较锡。
//獲取特性
var id = element.attributes.getNamedItem("id").nodeValue;
var id = element.attributes["id"].nodeValue;
//修改特性
element.attributes["id"].nodeValue = "someOtherId";
var oldAttr = element.attributes.removeNamedItem("id");
element.attributes.setNamedItem(newAttr);
創(chuàng)建元素
var div = document.createElement("div");
div.id = "myNewDiv";
div.className = "box";
document.body.appendChild(div);
//在IE中也可以這樣
var div = document.createElement("<div id=\"myNewDiv\" class=\"box\"></div >");
遍歷子節(jié)點
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<ul id="myList"><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>
對于上面的兩個ul元素childNodes包含的數(shù)目可能不同俯邓,IE外的瀏覽器對第一個會包含3個li和4個文本節(jié)點。如果你真的要遍歷朦蕴,你可以在遍歷的同時判斷下節(jié)點的類型。
Text類型
包含純文本內(nèi)容
- nodeType:3
- nodeName:"#text"
- nodeValue:文本內(nèi)容
- parentNode:Element
- ownerDocument:Document
- 無子節(jié)點
<div>Hello World!</div>
var textNode = div.firstChild;
//獲取或設(shè)置文本的值
div.firstChild.nodeValue = "Some other message";
div.firstChild.data = "Some other message";
創(chuàng)建文本節(jié)點
var element = document.createElement("div");
element.className = "message";
//這里的大于小于號之類的會被轉(zhuǎn)義伦连,不會被解釋為元素標簽
var textNode = document.createTextNode("<strong>Hello</strong> world!");
element.appendChild(textNode);
//多個相鄰文本節(jié)點會被拼接
var anotherTextNode = document.createTextNode("Yippee!");
element.appendChild(anotherTextNode);
document.body.appendChild(element);
//多個相鄰節(jié)點導(dǎo)致混亂
alert(element.childNodes.length); //2
//這個方法合并相鄰文本節(jié)點
element.normalize();
alert(element.childNodes.length); //1
alert(element.firstChild.nodeValue);
分割文本節(jié)點
被分割出來的新節(jié)點于原來的有相同的父節(jié)點额港。
var element = document.createElement("div");
element.className = "message";
var textNode = document.createTextNode("Hello world!");
element.appendChild(textNode);
document.body.appendChild(element);
var newNode = element.firstChild.splitText(5);
其他方法
appendData(text)
deleteData(offset, count)
insertData(offset, text)
replaceData(offset, count, text)
substringData(offset, count)
length
這些方法都是用來獲取或修改文本節(jié)點中的內(nèi)容的。
Comment類型
在DOM中的注釋通過這個類型來表示
- nodeType:8
- nodeName:"#comment"
- nodeValue:注釋文本內(nèi)容
- parentNode:Document或Element
- ownerDocument:Document
- 無子節(jié)點
- 與text類型有相同的基類叹哭,所以上面的字符串方法它也有
<div id="myDiv"><!--A comment --></div>
var div = document.getElementById("myDiv");
var comment = div.firstChild;
alert(comment.data);
CDATASection類型
DocumentType類型
DocumentFragment類型
這個類型在文檔中沒有對應(yīng)的標記,是一個輕量級的文檔,可以包含和控制節(jié)點。它不能被直接添加到文檔中盈滴,但是可以作為一個倉庫來使用,在里面保存將來會用到的節(jié)點。
- nodeType:11
- nodeName:"#document-fragment"
- nodeValue:null
- parentNode:null
- ownerDocument:Document
- 子節(jié)點可以是Element咬展、ProcessingInstruction瞒斩、Comment破婆、Text济瓢、CDATASection妹卿、EntityReference
Node的各種DOM節(jié)點操作方法文檔片段都繼承了。
文檔片段的一個使用場景就是當我們循環(huán)添加節(jié)點時夺克,如果直接將節(jié)點添加到文檔中箕宙,那么每次添加都會導(dǎo)致瀏覽器的重新渲染铺纽。如果我們先把要循環(huán)的節(jié)點添加到一個文檔片段中陷寝,拼好了再添加到文檔中呢,這個問題就解決了褐奥。
var fragment = document.createDocumentFragment();
var ul = document.getElementById("myList");
var li = null;
for (var i=0; i < 3; i++){
li = document.createElement("li");
li.appendChild(document.createTextNode("Item " + (i+1)));
fragment.appendChild(li);
}
//當你將一個文檔片段添加到文檔中時,會把文檔片段中的所有節(jié)點添加至文檔的當前位置,而文檔片段本身永遠不會出現(xiàn)在文檔中
ul.appendChild(fragment);
Attr類型
元素的特性在DOM中以Attr類型來表示咖耘。從技術(shù)角度講,特性就是存在于元素的attributes中的節(jié)點撬码。盡管它們是節(jié)點儿倒,但不認為它們是DOM文檔樹的一部分。
var ul = document.getElementById("myList");
//特性節(jié)點不會出現(xiàn)在文檔樹中
alert(ul.childNodes.length);
alert(ul.attributes["class"].value);
//alert(ul.attributes["align"].value);會報錯
//添加特性節(jié)點
var attr = document.createAttribute("align");
attr.value = "left";
ul.setAttributeNode(attr);
alert(ul.attributes["align"].value);
一般只使用之前提到的getAttribute()呜笑、setAttribute()夫否、remveAttribute()方法找筝,很少直接引用特性節(jié)點。
DOM操作技術(shù)
動態(tài)腳本
指的是頁面加載時并不存在慷吊,但將來的某一時刻通過修改DOM動態(tài)添加的腳本袖裕。動態(tài)添加同樣有兩種方式,添加外部文件和行內(nèi)代碼溉瓶。
外部文件:
function loadScript(url){
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
document.body.appendChild(script);
}
loadScript("client.js");
腳本加載完成就可以使用了急鳄,但是這里我們并不能判斷是否加載完成了,需要借助一些事件來判斷堰酿。
行內(nèi)代碼:
function loadScriptString(code){
var script = document.createElement("script");
script.type = "text/javascript";
try {
//由于IE將script視作一個特殊的元素疾宏,不允許DOM訪問其子節(jié)點,這樣在IE中會報錯
script.appendChild(document.createTextNode(code));
} catch (ex){
//直接設(shè)置text屬性有的瀏覽器不支持触创,但IE肯定支持
script.text = code;
}
document.body.appendChild(script);
}
loadScriptString("function sayHi(){alert('hi');}");
動態(tài)樣式
外部文件:
function loadStyles(url){
var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = url;
var head = document.getElementsByTagName("head")[0];
head.appendChild(link);
}
loadStyles("styles.css");
內(nèi)聯(lián)樣式:
IE同樣有BUG
function loadStyleString(css){
var style = document.createElement("style");
style.type = "text/css";
try{
style.appendChild(document.createTextNode(css));
} catch (ex){
style.styleSheet.cssText = css;
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);
}
loadStyleString("body{background-color:red}");
操作表格
DOM為表格添加了一些屬性和方法坎藐,以便我們可以更好的操作表格。
table元素的:
caption
tBodies
tFoot
tHead
rows
createTHead()
createTFoot()
createCaption()
deleteTFoot()
deleteCaption()
deleteRow(pos)
insertRow(pos)
body元素的:
rows
deleteRow(pos)
insertRow(pos)
cells
deleteCell(pos)
insertCell(pos)
使用NodeList
NodeList以及其近親NamedNodeMap和HTMLCollection都是動態(tài)的哼绑,也就是說每當文檔發(fā)生變化時岩馍,對他們的查詢始終返回最新的信息。這也就導(dǎo)致了一些問題抖韩,
var divs = document.getElementsByTagName("div"),
i,
div;
for (i=0; i < divs.length; i++){
div = document.createElement("div");
document.body.appendChild(div);
}
在這段代碼中每次增加一個div節(jié)點蛀恩,divs中的元素也會同時增加一個,divs.length永遠會比當前的循環(huán)大茂浮。
// 這樣就不會有問題了
var divs = document.getElementsByTagName("div"),
i,
len,
div;
var divtemp = divs;
for (i=0, len=divs.length; i < len; i++){
div = document.createElement("div");
document.body.appendChild(div);
}
這也提醒了我們双谆,使用nodeList要謹慎,因為每次使用都會引起一次查詢席揽。