[TOC]
## DOM的概念
DOM是文檔對象模型(Document Object Model)的簡稱,它的基本思想是把結構化文檔(比如HTML和XML)解析成一系列的節點,再由這些節點組成一個樹狀結構(DOM Tree)。所有的節點和最終的樹狀結構,都有規范的對外接口,以達到使用編程語言操作文檔的目的(比如增刪內容)。所以,DOM可以理解成文檔(HTML文檔、XML文檔和SVG文檔)的編程接口。
DOM有自己的國際標準,目前的通用版本是[DOM 3](http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html),下一代版本[DOM 4](http://www.w3.org/TR/dom/)正在擬定中。本章介紹的就是JavaScript對DOM標準的實現和用法。
嚴格地說,DOM不屬于JavaScript,但是操作DOM是JavaScript最常見的任務,而JavaScript也是最常用于DOM操作的語言。所以,DOM往往放在JavaScript里面介紹。
## 節點的概念
DOM的最小組成單位叫做節點(node),一個文檔的樹形結構(DOM樹),就是由各種不同類型的節點組成。
對于HTML文檔,節點主要有以下六種類型:Document節點、DocumentType節點、Element節點、Attribute節點、Text節點和DocumentFragment節點。
| 節點 | 名稱 | 含義 |
| --- | --- | --- |
| Document | 文檔節點 | 整個文檔(window.document) |
| DocumentType | 文檔類型節點 | 文檔的類型(比如) |
| Element | 元素節點 | HTML元素(比如、等) |
| Attribute | 屬性節點 | HTML元素的屬性(比如class="right") |
| Text | 文本節點 | HTML文檔中出現的文本 |
| DocumentFragment | 文檔碎片節點 | 文檔的片段 |
瀏覽器原生提供一個Node對象,上表所有類型的節點都是Node對象派生出來的。也就是說,它們都繼承了Node的屬性和方法。
## Node節點的屬性
### nodeName,nodeType
nodeName屬性返回節點的名稱,nodeType屬性返回節點的常數值。具體的返回值,可查閱下方的表格。
| 類型 | nodeName | nodeType |
| --- | --- | --- |
| DOCUMENT_NODE | #document | 9 |
| ELEMENT_NODE | 大寫的HTML元素名 | 1 |
| ATTRIBUTE_NODE | 等同于Attr.name | 2 |
| TEXT_NODE | #text | 3 |
| DOCUMENT_FRAGMENT_NODE | #document-fragment | 11 |
| DOCUMENT_TYPE_NODE | 等同于DocumentType.name | 10 |
以document節點為例,它的nodeName屬性等于#document,nodeType屬性等于9。
~~~
document.nodeName // "#document"
document.nodeType // 9
~~~
通常來說,使用nodeType屬性確定一個節點的類型,比較方便。
~~~
document.querySelector('a').nodeType === 1
// true
document.querySelector('a').nodeType === Node.ELEMENT_NODE
// true
~~~
上面兩種寫法是等價的。
### ownerDocument,nextSibling,previousSibling,parentNode,parentElement
以下屬性返回當前節點的相關節點。
(1)ownerDocument
ownerDocument屬性返回當前節點所在的頂層文檔對象,即document對象。
~~~
var d = p.ownerDocument;
d === document // true
~~~
document對象本身的ownerDocument屬性,返回null。
(2)nextSibling
nextsibling屬性返回緊跟在當前節點后面的第一個同級節點。如果當前節點后面沒有同級節點,則返回null。注意,該屬性還包括文本節點和評論節點。因此如果當前節點后面有空格,該屬性會返回一個文本節點,內容為空格。
~~~
var el = document.getelementbyid('div-01').firstchild;
var i = 1;
while (el) {
console.log(i + '. ' + el.nodename);
el = el.nextsibling;
i++;
}
~~~
上面代碼遍歷div-01節點的所有子節點。
(3)previousSibling
previoussibling屬性返回當前節點前面的、距離最近的一個同級節點。如果當前節點前面沒有同級節點,則返回null。
~~~
// html代碼如下
// <a><b1 id="b1"/><b2 id="b2"/></a>
document.getelementbyid("b1").previoussibling // null
document.getelementbyid("b2").previoussibling.id // "b1"
~~~
對于當前節點前面有空格,則previoussibling屬性會返回一個內容為空格的文本節點。
(4)parentNode
parentNode屬性返回當前節點的父節點。對于一個節點來說,它的父節點只可能是三種類型:element節點、document節點和documentfragment節點。
下面代碼是如何從父節點移除指定節點。
~~~
if (node.parentNode) {
node.parentNode.removeChild(node);
}
~~~
對于document節點和documentfragment節點,它們的父節點都是null。另外,對于那些生成后還沒插入DOM樹的節點,父節點也是null。
(5)parentElement
parentElement屬性返回當前節點的父Element節點。如果當前節點沒有父節點,或者父節點類型不是Element節點,則返回null。
~~~
if (node.parentElement) {
node.parentElement.style.color = "red";
}
~~~
上面代碼設置指定節點的父Element節點的CSS屬性。
在IE瀏覽器中,只有Element節點才有該屬性,其他瀏覽器則是所有類型的節點都有該屬性。
### textContent,nodeValue
以下屬性返回當前節點的內容。
(1)textContent
textContent屬性返回當前節點和它的所有后代節點的文本內容。
~~~
// HTML代碼為
// <div id="divA">This is <span>some</span> text</div>
document.getElementById("divA").textContent
// This is some text
~~~
上面代碼的textContent屬性,自動忽略當前節點內部的HTML標簽,返回所有文本內容。
該屬性是可讀寫的,設置該屬性的值,會用一個新的文本節點,替換所有它原來的子節點。它還有一個好處,就是自動對HTML標簽轉義。這很適合用于用戶提供的內容。
~~~
document.getElementById('foo').textContent = '<p>GoodBye!</p>';
~~~
上面代碼在插入文本時,會將p標簽解釋為文本,即<p>,而不會當作標簽處理。
對于Text節點和Comment節點,該屬性的值與nodeValue屬性相同。對于其他類型的節點,該屬性會將每個子節點的內容連接在一起返回,但是不包括Comment節點。如果一個節點沒有子節點,則返回空字符串。
document節點和doctype節點的textContent屬性為null。如果要讀取整個文檔的內容,可以使用`document.documentElement.textContent`。
在IE瀏覽器,所有Element節點都有一個innerText屬性。它與textContent屬性基本相同,但是有幾點區別。
* innerText受CSS影響,textcontent不受。比如,如果CSS規則隱藏(hidden)了某段文本,innerText就不會返回這段文本,textcontent則照樣返回。
* innerText返回的文本,會過濾掉空格、換行和回車鍵,textcontent則不會。
* innerText屬性不是DOM標準的一部分,Firefox瀏覽器甚至沒有部署這個屬性,而textcontent是DOM標準的一部分。
(2)nodeValue
nodeValue屬性返回或設置當前節點的值,格式為字符串。但是,該屬性只對Text節點、Comment節點、XML文檔的CDATA節點有效,其他類型的節點一律返回null。
因此,nodeValue屬性一般只用于Text節點。對于那些返回null的節點,設置nodeValue屬性是無效的。
### childNodes,firstNode,lastChild
以下屬性返回當前節點的子節點。
(1)childNodes
childNodes屬性返回一個NodeList集合,成員包括當前節點的所有子節點。注意,除了HTML元素節點,該屬性返回的還包括Text節點和Comment節點。如果當前節點不包括任何子節點,則返回一個空的NodeList集合。由于NodeList對象是一個動態集合,一旦子節點發生變化,立刻會反映在返回結果之中。
~~~
var ulElementChildNodes = document.querySelector('ul').childNodes;
~~~
(2)firstNode
firstNode屬性返回當前節點的第一個子節點,如果當前節點沒有子節點,則返回null。注意,除了HTML元素子節點,該屬性還包括文本節點和評論節點。
(3)lastChild
lastChild屬性返回當前節點的最后一個子節點,如果當前節點沒有子節點,則返回null。
### baseURI
baseURI屬性返回一個字符串,由當前網頁的協議、域名和所在的目錄組成,表示當前網頁的絕對路徑。如果無法取到這個值,則返回null。瀏覽器根據這個屬性,計算網頁上的相對路徑的URL。該屬性為只讀。
通常情況下,該屬性由當前網址的URL(即window.location屬性)決定,但是可以使用HTML的標簽,改變該屬性的值。
~~~
<base href="http://www.example.com/page.html">
<base target="_blank" href="http://www.example.com/page.html">
~~~
該屬性不僅document對象有(`document.baseURI`),元素節點也有(`element.baseURI`)。通常情況下,它們的值是相同的。
## Node節點的方法
### appendChild(),hasChildNodes()
以下方法與子節點相關。
(1)appendChild()
appendChild方法接受一個節點對象作為參數,將其作為最后一個子節點,插入當前節點。
~~~
var p = document.createElement("p");
document.body.appendChild(p);
~~~
如果參數節點是文檔中現有的其他節點,appendChild方法會將其從原來的位置,移動到新位置。
hasChildNodes方法返回一個布爾值,表示當前節點是否有子節點。
~~~
var foo = document.getElementById("foo");
if ( foo.hasChildNodes() ) {
foo.removeChild( foo.childNodes[0] );
}
~~~
上面代碼表示,如果foo節點有子節點,就移除第一個子節點。
(2)hasChildNodes()
hasChildNodes方法結合firstChild屬性和nextSibling屬性,可以遍歷當前節點的所有后代節點。
~~~
function DOMComb (oParent, oCallback) {
if (oParent.hasChildNodes()) {
for (var oNode = oParent.firstChild; oNode; oNode = oNode.nextSibling) {
DOMComb(oNode, oCallback);
}
}
oCallback.call(oParent);
}
~~~
上面代碼的DOMComb函數的第一個參數是某個指定的節點,第二個參數是回調函數。這個回調函數會依次作用于指定節點,以及指定節點的所有后代節點。
~~~
function printContent () {
if (this.nodeValue) {
console.log(this.nodeValue);
}
}
DOMComb(document.body, printContent);
~~~
### cloneNode(),insertBefore(),removeChild(),replaceChild()
下面方法與節點操作有關。
(1)cloneNode()
cloneNode方法用于克隆一個節點。它接受一個布爾值作為參數,表示是否同時克隆子節點,默認是false,即不克隆子節點。
~~~
var cloneUL = document.querySelector('ul').cloneNode(true);
~~~
需要注意的是,克隆一個節點,會拷貝該節點的所有屬性,但是會喪失addEventListener方法和on-屬性(即`node.onclick = fn`),添加在這個節點上的事件回調函數。
克隆一個節點之后,DOM樹有可能出現兩個有相同ID屬性(即`id="xxx"`)的HTML元素,這時應該修改其中一個HTML元素的ID屬性。
(2)insertBefore()
insertBefore方法用于將某個節點插入當前節點的指定位置。它接受兩個參數,第一個參數是所要插入的節點,第二個參數是當前節點的一個子節點,新的節點將插在這個節點的前面。該方法返回被插入的新節點。
~~~
var text1 = document.createTextNode('1');
var li = document.createElement('li');
li.appendChild(text1);
var ul = document.querySelector('ul');
ul.insertBefore(li,ul.firstChild);
~~~
上面代碼在ul節點的最前面,插入一個新建的li節點。
如果insertBefore方法的第二個參數為null,則新節點將插在當前節點的最后位置,即變成最后一個子節點。
將新節點插在當前節點的最前面(即變成第一個子節點),可以使用當前節點的firstChild屬性。
~~~
parentElement.insertBefore(newElement, parentElement.firstChild);
~~~
上面代碼中,如果當前節點沒有任何子節點,`parentElement.firstChild`會返回null,則新節點會插在當前節點的最后,等于是第一個子節點。
由于不存在insertAfter方法,如果要插在當前節點的某個子節點后面,可以用insertBefore方法結合nextSibling屬性模擬。
~~~
parentDiv.insertBefore(s1, s2.nextSibling);
~~~
上面代碼可以將s1節點,插在s2節點的后面。如果s2是當前節點的最后一個子節點,則`s2.nextSibling`返回null,這時s1節點會插在當前節點的最后,變成當前節點的最后一個子節點,等于緊跟在s2的后面。
(3)removeChild()
removeChild方法接受一個子節點作為參數,用于從當前節點移除該節點。它返回被移除的節點。
~~~
var divA = document.getElementById('A');
divA.parentNode.removeChild(divA);
~~~
上面代碼是如何移除一個指定節點。
下面是如何移除當前節點的所有子節點。
~~~
var element = document.getElementById("top");
while (element.firstChild) {
element.removeChild(element.firstChild);
}
~~~
被移除的節點依然存在于內存之中,但是不再是DOM的一部分。所以,一個節點移除以后,依然可以使用它,比如插入到另一個節點。
(4)replaceChild()
replaceChild方法用于將一個新的節點,替換當前節點的某一個子節點。它接受兩個參數,第一個參數是用來替換的新節點,第二個參數將要被替換走的子節點。它返回被替換走的那個節點。
~~~
replacedNode = parentNode.replaceChild(newChild, oldChild);
~~~
下面是一個例子。
~~~
var divA = document.getElementById('A');
var newSpan = document.createElement('span');
newSpan.textContent = 'Hello World!';
divA.parentNode.replaceChild(newSpan,divA);
~~~
上面代碼是如何替換指定節點。
### contains(),compareDocumentPosition(),isEqualNode()
下面方法用于節點的互相比較。
(1)contains()
contains方法接受一個節點作為參數,返回一個布爾值,表示參數節點是否為當前節點的后代節點。
~~~
document.body.contains(node)
~~~
上面代碼檢查某個節點,是否包含在當前文檔之中。
注意,如果將當前節點傳入contains方法,會返回true。雖然從意義上說,一個節點不應該包含自身。
~~~
nodeA.contains(nodeA) // true
~~~
(2)compareDocumentPosition()
compareDocumentPosition方法的用法,與contains方法完全一致,返回一個7個比特位的二進制值,表示參數節點與當前節點的關系。
| 二進制值 | 數值 | 含義 |
| --- | --- | --- |
| 000000 | 0 | 兩個節點相同 |
| 000001 | 1 | 兩個節點不在同一個文檔(即有一個節點不在當前文檔) |
| 000010 | 2 | 參數節點在當前節點的前面 |
| 000100 | 4 | 參數節點在當前節點的后面 |
| 001000 | 8 | 參數節點包含當前節點 |
| 010000 | 16 | 當前節點包含參數節點 |
| 100000 | 32 | 瀏覽器的私有用途 |
~~~
// HTML代碼為
// <div id="writeroot">
// <form>
// <input id="test" />
// </form>
// </div>
var x = document.getElementById('writeroot');
var y = document.getElementById('test');
x.compareDocumentPosition(y) // 20
y.compareDocumentPosition(x) // 10
~~~
上面代碼中,節點x包含節點y,而且節點y在節點x的后面,所以第一個compareDocumentPosition方法返回20(010100),第二個compareDocumentPosition方法返回10(0010010)。
由于compareDocumentPosition返回值的含義,定義在每一個比特位上,所以如果要檢查某一種特定的含義,就需要使用比特位運算符。
~~~
var head = document.head;
var body = document.body;
if (head.compareDocumentPosition(body) & 4) {
console.log("文檔結構正確");
} else {
console.log("<head> 不能在 <body> 前面");
}
~~~
上面代碼中,compareDocumentPosition的返回值與4(又稱掩碼)進行與運算(&),得到一個布爾值,表示head是否在body前面。
在這個方法的基礎上,可以部署一些特定的函數,檢查節點的位置。
~~~
Node.prototype.before = function (arg) {
return !!(this.compareDocumentPosition(arg) & 2)
}
nodeA.before(nodeB)
~~~
上面代碼在Node對象上部署了一個before方法,返回一個布爾值,表示參數節點是否在當前節點的前面。
(3)isEqualNode()
isEqualNode方法返回一個布爾值,用于檢查兩個節點是否相等。所謂相等的節點,指的是兩個節點的類型相同、屬性相同、子節點相同。
~~~
var targetEl = document.getElementById("targetEl");
var firstDiv = document.getElementsByTagName("div")[0];
targetEl.isEqualNode(firstDiv)
~~~
### normalize()
normailize方法用于清理當前節點內部的所有Text節點。它會去除空的文本節點,并且將毗鄰的文本節點合并成一個。
~~~
var wrapper = document.createElement("div");
wrapper.appendChild(document.createTextNode("Part 1 "));
wrapper.appendChild(document.createTextNode("Part 2 "));
wrapper.childNodes.length // 2
wrapper.normalize();
wrapper.childNodes.length // 1
~~~
上面代碼使用normalize方法之前,wrapper節點有兩個Text子節點。使用normalize方法之后,兩個Text子節點被合并成一個。
該方法是`Text.splitText`的逆方法,可以查看《Text節點》章節,了解更多內容。
## NodeList接口,HTMLCollection接口
節點對象都是單個節點,但是有時會需要一種數據結構,能夠容納多個節點。DOM提供兩種接口,用于部署這種節點的集合:NodeList接口和HTMLCollection接口。
### NodeList接口
有些屬性和方法返回的是一組節點,比如Node.childNodes、document.querySelectorAll()。它們返回的都是一個部署了NodeList接口的對象。
NodeList接口有時返回一個動態集合,有時返回一個靜態集合。所謂動態集合就是一個活的集合,DOM樹刪除或新增一個相關節點,都會立刻反映在NodeList接口之中。Node.childNodes返回的,就是一個動態集合。
~~~
var parent = document.getElementById('parent');
parent.childNodes.length // 2
parent.appendChild(document.createElement('div'));
parent.childNodes.length // 3
~~~
上面代碼中,`parent.childNodes`返回的是一個部署了NodeList接口的對象。當parent節點新增一個子節點以后,該對象的成員個數就增加了1。
document.querySelectorAll方法返回的是一個靜態,DOM內部的變化,并不會實時反映在該方法的返回結果之中。
NodeList接口提供length屬性和數字索引,因此可以像數組那樣,使用數字索引取出每個節點,但是它本身并不是數組,不能使用pop或push之類數組特有的方法。
~~~
// 數組的繼承鏈
myArray --> Array.prototype --> Object.prototype --> null
// NodeList的繼承鏈
myNodeList --> NodeList.prototype --> Object.prototype --> null
~~~
從上面的繼承鏈可以看到,NodeList接口對象并不繼承Array.prototype,因此不具有數組接口提供的方法。如果要在NodeList接口使用數組方法,可以將NodeList接口對象轉為真正的數組。
~~~
var div_list = document.querySelectorAll('div');
var div_array = Array.prototype.slice.call(div_list);
~~~
也可以通過下面的方法調用。
~~~
var forEach = Array.prototype.forEach;
forEach.call(element.childNodes, function(child){
child.parentNode.style.color = '#0F0';
});
~~~
上面代碼讓數組的forEach方法在NodeList接口對象上調用。
不過,遍歷NodeList接口對象的首選方法,還是使用for循環。
~~~
for (var i = 0; i < myNodeList.length; ++i) {
var item = myNodeList[i];
}
~~~
不要使用for...in循環去遍歷NodeList接口對象,因為for...in循環會將非數字索引的length屬性和下面要講到的item方法,也遍歷進去,而且不保證各個成員遍歷的順序。
ES6新增的for...of循環,也可以正確遍歷NodeList接口對象。
~~~
var list = document.querySelectorAll( 'input[type=checkbox]' );
for (var item of list) {
item.checked = true;
}
~~~
NodeList接口提供item方法,接受一個數字索引作為參數,返回該索引對應的成員。如果取不到成員,或者索引不合法,則返回null。
~~~
nodeItem = nodeList.item(index)
// 實例
var divs = document.getElementsByTagName("div");
var secondDiv = divs.item(1);
~~~
上面代碼中,由于數字索引從零開始計數,所以取出第二個成員,要使用數字索引1。
所有類似數組的對象,都可以使用方括號運算符取出成員,所以一般情況下,都是使用下面的寫法,而不使用item方法。
~~~
nodeItem = nodeList[index]
~~~
### HTMLCollection接口
HTMLCollection接口與NodeList接口類似,也是節點的集合,但是集合成員都是Element節點。該接口都是動態集合,節點的變化會實時反映在集合中。document.links、docuement.forms、document.images等屬性,返回的都是HTMLCollection接口對象。
部署了該接口的對象,具有length屬性和數字索引,因此是一個類似于數組的對象。
item方法根據成員的位置參數(從0開始),返回該成員。如果取不到成員或數字索引不合法,則返回null。
~~~
var c = document.images;
var img1 = c.item(10);
// 等價于下面的寫法
var img1 = c[1];
~~~
namedItem方法根據成員的ID屬性或name屬性,返回該成員。如果沒有對應的成員,則返回null。
~~~
// HTML代碼為
// <form id="myForm"></form>
var elem = document.forms.namedItem("myForm");
// 等價于下面的寫法
var elem = document.forms["myForm"];
~~~
由于item方法和namedItem方法,都可以用方括號運算符代替,所以建議一律使用方括號運算符。
## ParentNode接口,ChildNode接口
不同的節點除了繼承Node接口以外,還會繼承其他接口。ParentNode接口用于獲取當前節點的Element子節點,ChildNode接口用于處理當前節點的子節點(包含但不限于Element子節點)。
### ParentNode接口
ParentNode接口用于獲取Element子節點。Element節點、Document節點和DocumentFragment節點,部署了ParentNode接口。凡是這三類節點,都具有以下四個屬性,用于獲取Element子節點。
(1)children
children屬性返回一個動態的HTMLCollection集合,由當前節點的所有Element子節點組成。
下面代碼遍歷指定節點的所有Element子節點。
~~~
if (el.children.length) {
for (var i = 0; i < el.children.length; i++) {
// ...
}
}
~~~
(2)firstElementChild
firstChild屬性返回當前節點的第一個Element子節點,如果不存在任何Element子節點,則返回null。
~~~
document.firstElementChild.nodeName
// "HTML"
~~~
上面代碼中,document節點的第一個Element子節點是。
(3)lastElementChild
lastElementChild屬性返回當前節點的最后一個Element子節點,如果不存在任何Element子節點,則返回null。
~~~
document.lastElementChild.nodeName
// "HTML"
~~~
上面代碼中,document節點的最后一個Element子節點是。
(4)childElementCount
childElementCount屬性返回當前節點的所有Element子節點的數目。
### ChildNode接口
ChildNode接口用于處理子節點(包含但不限于Element子節點)。Element節點、DocumentType節點和CharacterData接口,部署了ChildNode接口。凡是這三類節點(接口),都可以使用下面四個方法。但是現實的情況是,除了第一個remove方法,目前沒有瀏覽器支持后面三個方法。
(1)remove()
remove方法用于移除當前節點。
~~~
el.remove()
~~~
上面方法在DOM中移除了el節點。注意,調用這個方法的節點,是被移除的節點本身,而不是它的父節點。
(2)before()
before方法用于在當前節點的前面,插入一個同級節點。如果參數是節點對象,插入DOM的就是該節點對象;如果參數是文本,插入DOM的就是參數對應的文本節點。
(3)after()
after方法用于在當前節點的后面,插入一個同級節點。如果參數是節點對象,插入DOM的就是該節點對象;如果參數是文本,插入DOM的就是參數對應的文本節點。
(4)replaceWith()
replaceWith方法使用參數指定的節點,替換當前節點。如果參數是節點對象,替換當前節點的就是該節點對象;如果參數是文本,替換當前節點的就是參數對應的文本節點。
## html元素
html元素是網頁的根元素,document.documentElement就指向這個元素。
(1)clientWidth屬性,clientHeight屬性
這兩個屬性返回視口(viewport)的大小,單位為像素。所謂“視口”,是指用戶當前能夠看見的那部分網頁的大小
document.documentElement.clientWidth和document.documentElement.clientHeight,基本上與window.innerWidth和window.innerHeight同義。只有一個區別,前者不將滾動條計算在內(很顯然,滾動條和工具欄會減小視口大小),而后者包括了滾動條的高度和寬度。
(2)offsetWidth屬性,offsetHeight屬性
這兩個屬性返回html元素的寬度和高度,即網頁的總寬度和總高度。
### dataset屬性
dataset屬性用于操作HTML標簽元素的data-*屬性。目前,Firefox、Chrome、Opera、Safari瀏覽器支持該API。
假設有如下的網頁代碼。
~~~
<div id="myDiv" data-id="myId"></div>
~~~
以data-id屬性為例,要讀取這個值,可以用dataset.id。
~~~
var id = document.getElementById("myDiv").dataset.id;
~~~
要設置data-id屬性,可以直接對dataset.id賦值。這時,如果data-id屬性不存在,將會被創造出來。
~~~
document.getElementById("myDiv").dataset.id = "hello";
~~~
刪除一個data-*屬性,可以直接使用delete命令。
~~~
delete document.getElementById("myDiv").dataset.id
~~~
IE 9不支持dataset屬性,可以用 getAttribute('data-foo')、removeAttribute('data-foo')、setAttribute('data-foo')、hasAttribute('data-foo') 代替。
需要注意的是,dataset屬性使用駱駝拼寫法表示屬性名,這意味著data-hello-world會用dataset.helloWorld表示。而如果此時存在一個data-helloWorld屬性,該屬性將無法讀取,也就是說,data屬性本身只能使用連詞號,不能使用駱駝拼寫法。
### tabindex屬性
tabindex屬性用來指定,當前HTML元素節點是否被tab鍵遍歷,以及遍歷的優先級。
~~~
var b1 = document.getElementById("button1");
b1.tabIndex = 1;
~~~
如果 tabindex = -1 ,tab鍵跳過當前元素。
如果 tabindex = 0 ,表示tab鍵將遍歷當前元素。如果一個元素沒有設置tabindex,默認值就是0。
如果 tabindex 大于0,表示tab鍵優先遍歷。值越大,就表示優先級越大。
### 頁面位置相關屬性
(1)offsetParent屬性、offsetTop屬性和offsetLeft屬性
這三個屬性提供Element對象在頁面上的位置。
* offsetParent:當前HTML元素的最靠近的、并且CSS的position屬性不等于static的父元素。
* offsetTop:當前HTML元素左上角相對于offsetParent的垂直位移。
* offsetLeft:當前HTML元素左上角相對于offsetParent的水平位移。
如果Element對象的父對象都沒有將position屬性設置為非static的值(比如absolute或relative),則offsetParent屬性指向body元素。另外,計算offsetTop和offsetLeft的時候,是從邊框的左上角開始計算,即Element對象的border寬度不計入offsetTop和offsetLeft。
### style屬性
style屬性用來讀寫頁面元素的行內CSS屬性,詳見本章《CSS操作》一節。
### Element對象的方法
(1)選擇子元素的方法
Element對象也部署了document對象的4個選擇子元素的方法,而且用法完全一樣。
* querySelector方法
* querySelectorAll方法
* getElementsByTagName方法
* getElementsByClassName方法
上面四個方法只用于選擇Element對象的子節點。因此,可以采用鏈式寫法來選擇子節點。
~~~
document.getElementById('header').getElementsByClassName('a')
~~~
各大瀏覽器對這四個方法都支持良好,IE的情況如下:IE 6開始支持getElementsByTagName,IE 8開始支持querySelector和querySelectorAll,IE 9開始支持getElementsByClassName。
(2)elementFromPoint方法
該方法用于選擇在指定坐標的最上層的Element對象。
~~~
document.elementFromPoint(50,50)
~~~
上面代碼了選中在(50,50)這個坐標的最上層的那個HTML元素。
(3)HTML元素的屬性相關方法
* hasAttribute():返回一個布爾值,表示Element對象是否有該屬性。
* getAttribute()
* setAttribute()
* removeAttribute()
(4)matchesSelector方法
該方法返回一個布爾值,表示Element對象是否符合某個CSS選擇器。
~~~
document.querySelector('li').matchesSelector('li:first-child')
~~~
這個方法需要加上瀏覽器前綴,需要寫成mozMatchesSelector()、webkitMatchesSelector()、oMatchesSelector()、msMatchesSelector()。
(5)focus方法
focus方法用于將當前頁面的焦點,轉移到指定元素上。
~~~
document.getElementById('my-span').focus();
~~~
### table元素
表格有一些特殊的DOM操作方法。
* insertRow():在指定位置插入一個新行(tr)。
* deleteRow():在指定位置刪除一行(tr)。
* insertCell():在指定位置插入一個單元格(td)。
* deleteCell():在指定位置刪除一個單元格(td)。
* createCaption():插入標題。
* deleteCaption():刪除標題。
* createTHead():插入表頭。
* deleteTHead():刪除表頭。
下面是使用JavaScript生成表格的一個例子。
~~~
var table = document.createElement('table');
var tbody = document.createElement('tbody');
table.appendChild(tbody);
for (var i = 0; i <= 9; i++) {
var rowcount = i + 1;
tbody.insertRow(i);
tbody.rows[i].insertCell(0);
tbody.rows[i].insertCell(1);
tbody.rows[i].insertCell(2);
tbody.rows[i].cells[0].appendChild(document.createTextNode('Row ' + rowcount + ', Cell 1'));
tbody.rows[i].cells[1].appendChild(document.createTextNode('Row ' + rowcount + ', Cell 2'));
tbody.rows[i].cells[2].appendChild(document.createTextNode('Row ' + rowcount + ', Cell 3'));
}
table.createCaption();
table.caption.appendChild(document.createTextNode('A DOM-Generated Table'));
document.body.appendChild(table);
~~~
這些代碼相當易讀,其中需要注意的就是insertRow和insertCell方法,接受一個表示位置的參數(從0開始的整數)。
table元素有以下屬性:
* caption:標題。
* tHead:表頭。
* tFoot:表尾。
* rows:行元素對象,該屬性只讀。
* rows.cells:每一行的單元格對象,該屬性只讀。
* tBodies:表體,該屬性只讀。
## 參考鏈接
* Louis Lazaris,?[Thinking Inside The Box With Vanilla JavaScript](http://coding.smashingmagazine.com/2013/10/06/inside-the-box-with-vanilla-javascript/)
* David Walsh,?[HTML5 classList API](http://davidwalsh.name/classlist)
* Derek Johnson,?[The classList API](http://html5doctor.com/the-classlist-api/)
* Mozilla Developer Network,?[element.dataset API](http://davidwalsh.name/element-dataset)
* David Walsh,?[The element.dataset API](http://davidwalsh.name/element-dataset)
- 第一章 導論
- 1.1 前言
- 1.2 為什么學習JavaScript?
- 1.3 JavaScript的歷史
- 第二章 基本語法
- 2.1 語法概述
- 2.2 數值
- 2.3 字符串
- 2.4 對象
- 2.5 數組
- 2.6 函數
- 2.7 運算符
- 2.8 數據類型轉換
- 2.9 錯誤處理機制
- 2.10 JavaScript 編程風格
- 第三章 標準庫
- 3.1 Object對象
- 3.2 Array 對象
- 3.3 包裝對象和Boolean對象
- 3.4 Number對象
- 3.5 String對象
- 3.6 Math對象
- 3.7 Date對象
- 3.8 RegExp對象
- 3.9 JSON對象
- 3.10 ArrayBuffer:類型化數組
- 第四章 面向對象編程
- 4.1 概述
- 4.2 封裝
- 4.3 繼承
- 4.4 模塊化編程
- 第五章 DOM
- 5.1 Node節點
- 5.2 document節點
- 5.3 Element對象
- 5.4 Text節點和DocumentFragment節點
- 5.5 Event對象
- 5.6 CSS操作
- 5.7 Mutation Observer
- 第六章 瀏覽器對象
- 6.1 瀏覽器的JavaScript引擎
- 6.2 定時器
- 6.3 window對象
- 6.4 history對象
- 6.5 Ajax
- 6.6 同域限制和window.postMessage方法
- 6.7 Web Storage:瀏覽器端數據儲存機制
- 6.8 IndexedDB:瀏覽器端數據庫
- 6.9 Web Notifications API
- 6.10 Performance API
- 6.11 移動設備API
- 第七章 HTML網頁的API
- 7.1 HTML網頁元素
- 7.2 Canvas API
- 7.3 SVG 圖像
- 7.4 表單
- 7.5 文件和二進制數據的操作
- 7.6 Web Worker
- 7.7 SSE:服務器發送事件
- 7.8 Page Visibility API
- 7.9 Fullscreen API:全屏操作
- 7.10 Web Speech
- 7.11 requestAnimationFrame
- 7.12 WebSocket
- 7.13 WebRTC
- 7.14 Web Components
- 第八章 開發工具
- 8.1 console對象
- 8.2 PhantomJS
- 8.3 Bower:客戶端庫管理工具
- 8.4 Grunt:任務自動管理工具
- 8.5 Gulp:任務自動管理工具
- 8.6 Browserify:瀏覽器加載Node.js模塊
- 8.7 RequireJS和AMD規范
- 8.8 Source Map
- 8.9 JavaScript 程序測試
- 第九章 JavaScript高級語法
- 9.1 Promise對象
- 9.2 有限狀態機
- 9.3 MVC框架與Backbone.js
- 9.4 嚴格模式
- 9.5 ECMAScript 6 介紹
- 附錄
- 10.1 JavaScript API列表
- 草稿一:函數庫
- 11.1 Underscore.js
- 11.2 Modernizr
- 11.3 Datejs
- 11.4 D3.js
- 11.5 設計模式
- 11.6 排序算法
- 草稿二:jQuery
- 12.1 jQuery概述
- 12.2 jQuery工具方法
- 12.3 jQuery插件開發
- 12.4 jQuery.Deferred對象
- 12.5 如何做到 jQuery-free?
- 草稿三:Node.js
- 13.1 Node.js 概述
- 13.2 CommonJS規范
- 13.3 package.json文件
- 13.4 npm模塊管理器
- 13.5 fs 模塊
- 13.6 Path模塊
- 13.7 process對象
- 13.8 Buffer對象
- 13.9 Events模塊
- 13.10 stream接口
- 13.11 Child Process模塊
- 13.12 Http模塊
- 13.13 assert 模塊
- 13.14 Cluster模塊
- 13.15 os模塊
- 13.16 Net模塊和DNS模塊
- 13.17 Express框架
- 13.18 Koa 框架