對DOM的兩個主要擴充套件是Selectors API和HTML5。
選擇符API
Selectors API致力於讓瀏覽器原生支援CSS查詢。在沒有原生支援之前,只能通過javascript程式碼來完成查詢操作。之後,解析和樹查詢操作可以在瀏覽器內部通過編譯後的程式碼來完成,極大地改善了效能。
querySelector()方法
querySelector()
方法接收一個CSS選擇符,返回與該模式匹配的第一個元素,如果沒有找到匹配的元素,返回null。
通過Document型別呼叫時,會在文件元素的範圍內查詢匹配的元素。而通過Element型別呼叫時,只會在該元素後代元素的範圍內查詢匹配的元素。
<ul>
<li class='one'></li>
<li></li>
<li></li>
</ul>
$ul = document.querySelector('ul');
$ul.querySelector('.one')
// return <li class='one'></li>
複製程式碼
querySelectorAll()方法
querySelectorAll()方法接收的引數與querySelector()方法一樣,都是一個CSS選擇符,但返回的時所有匹配的元素。這個方法返回的時一個Nodelist例項。
<ul>
<li class='one'></li>
<li></li>
<li></li>
</ul>
$ul = document.querySelector('ul');
$ul.querySelectorAll('.two')
// NodeList[]
複製程式碼
可以呼叫上面兩種方法的node型別包括:document、element、DocumentFragment。
matchesSelector()方法
Element型別新增了一個方法matchesSelector()
。這個方法接收了一個引數,即CSS選擇符,如果呼叫元素與該選擇符匹配,返回true;否則,返回false。
這個方法還沒有被所有瀏覽器都支援。
元素遍歷
專門用於element型別的元素遍歷
- childElementCount
- firstElementChild
- lastElementChild
- perviousElementSibling
- nextElementSibling
HTML5
HTML5規範則圍繞如何使用新增標記定義了大量Javascript API。其中一些API和DOM重疊,定義了瀏覽器應該支援的DOM擴充套件。
與類相關的擴充
getElementsByClassName()方法
這個方法可以通過document物件以及所有HTML元素呼叫。
classList屬性
在操作類名時,需要通過className屬性新增、刪除和替換類名。因為className中是一個字串,所以即使只修改字串的一部分,也必須每次都設定整個字串的值。
<div class='a b c'></div>
var className = div.className.split(/\s+/);
var pos = -1;
var i;
var len;
for (i=0, len=className.length; i < len; i++) {
if (className[i] == 'a') {
pos = i;
break;
}
}
//刪除類名
className.splice(i, 1)
//把剩下的類名拼成字串並重新設定
div.className = className.join(' ');
複製程式碼
HTML5新增了一種操作類名的方式,可以讓操作更簡單也更安全,那就是為所有元素新增classList屬性。這個classList屬性是新集合型別DOMTokenList的例項。
DOMTokenList有一個表示自己包含多少元素的length屬性,而要取得每個元素可以使用item()方法,也可以使用方括號語法。此外這個新型別還定義如下方法。
- add(value): 將給定的字串值新增到列表中。如果值已經存在,就不新增了。
- contains(value): 表示列表中是否存在給定的值,如果存在則返回true,否則返回false。
- remove(value): 從列表中刪除給定的字串
- toggle(value): 如果列表中已經存在給定的值,刪除它;如果列表中沒有給定的值,新增它。
焦點管理
document.activeElement屬性,這個屬性始終引用了DOM中當前獲得了焦點的元素。元素獲得焦點的方式有頁面載入、使用者輸入和在程式碼中呼叫focus()方法。
var dom = document.getElementById('dom');
dom.focus();
document.activeElement === dom; // true
dom.hasFocus(); // true
複製程式碼
HTMLDocument變化
新增readyState屬性
Document的readyState屬性有兩個可能的值:
- loading,正在載入文件
- complete,已經載入文件 document.readyState屬性的基本用法如下:
if(document.readyState === 'complete') {
// 執行操作
}
複製程式碼
head屬性
新增document.body
方法,直接引用元素
字符集屬性
HTML5新增了幾個與文件字符集有關的屬性。其中,charset屬性表示文件中實際使用的字符集,也可以用來指定新字符集。
自定義資料屬性
HTML5規定可以為元素新增非標準的屬性,但要新增字首data-。
var dom = `<div id='myDiv' data-appid='234' data-myname='zc'></div>`;
var $container = document.createElement('div');
$container.innerHTML = dom;
var $dom = $container.firstElementChild;
$dom.dataset; // DOMStringMap {appid: "234", myname: "zc"}
$dom.dataset.appid = 12;
$dom.dataset; // DOMStringMap {appid: "12", myname: "zc"}
複製程式碼
插入標記
使用插入標記的技術,直接插入HTML字串不僅更簡單,速度也更快。以下與插入標記相關的DOM擴充套件已經納入了HTML5規範。
innerHTML屬性
在讀模式下,innerHTML屬性返回與呼叫元素的所有子節點(包括元素、註釋和文字節點)對應的HTML標記。在寫模式下,innerHTML會根據指定的值建立新的DOM樹,然後用這個DOM樹完全替換呼叫元素原先的所有子節點。
但是,不同瀏覽器返回的文字格式會有所不同。IE和Opera會將所有標籤轉換成大寫形式。而Safari、Chrome和Firefox則會原原本本地按照原先文件中的格式指定返回HTML,包括空格和縮排。
在寫模式下,innerHTML的值會被解析為DOM子樹,替換呼叫元素原來的所有子節點。因為它的值被認為是HTML,所以其中的所有標籤都會按照瀏覽器處理HTML的標準方式轉換為元素。
為innerHTML設定HTML字串後,瀏覽器會將這個字串解析為相應的DOM樹。因此設定了innerHTML之後,再從中讀取HTML字串,會得到與設定時不一樣的結果。原因在於返回的字串是根據原始HTML字串建立的DOM樹經過序列化之後的結果。
outerHTML屬性
在讀模式下,outerHTML返回撥用它的元素及所有子節點的HTML標籤。在寫模式下,outerHTML會根據指定的HTML字串建立新的DOM子樹,然後用這個DOM子樹完全替換呼叫元素。
insertAdjacentHTML()方法
插入標記的最後一個新增方式是insertAdjacentHTML()
方法。這個方法最早也是在IE中出現的,它接受兩個引數:插入位置和要插入的HTML文字。第一個引數必須是下列值之一:
beforeBegin
,在當前元素之前插入一個緊鄰的同輩元素
記憶體與效能問題
使用本節介紹的方法替換子節點可能會導致瀏覽器的記憶體佔用問題,尤其在IE中,問題更加明顯。在刪除帶有事件處理程式或引用了其他Javascript物件子樹時,就有可能導致記憶體佔用問題。假設某個元素有一個事件處理程式(或者引用了一個javascript物件作為屬性),在使用前述某個屬性將該元素從文件樹中刪除後,元素與事件處理程式(javascript物件)之間的繫結關係在記憶體中並沒有一併刪除。如果這種情況頻繁出現,頁面佔用的記憶體數量就會明顯增加。因此,在使用innerHTML、outerHTML、insertAdjacentHTML()方法時,最好先手工刪除要被替換的元素的所有事件處理程式和Javascript物件屬性。
同理: 每次迴圈都設定一次innerHTML的做法效率很低。
for (var i = 0; len = values.length; i < len; i++) {
ul.innerHTML += "<li>" + values[i] + "</li>"; // 要避免這種頻繁操作
}
複製程式碼
效率更高的:
for (var i = 0, len = values.length; i <len; i++) {
itemsHtml += "<li>" + values[i] + "</li>";
}
ul.innerHTML = itemsHTML;
複製程式碼
那些沒有標準化的專用擴充套件
文件模式
IE8引入了一個新的概念叫做“文件模式”(document mode)。頁面的文件模式決定了可以使用什麼功能。換句話說,文件模式決定了你可以使用哪個級別的CSS,可以在JavaScript中使用哪些API,以及如何對待文件型別(doctype)。到了IE9,總共有以下4種文件模式。
- IE5: 以混雜模式渲染頁面。
- IE7: 以IE7標準模式渲染頁面。IE8及更高版本中的新功能都無法使用。
- IE8: 以IE8標準模式渲染頁面。IE8中的新功能都可以使用,因此可以使用Selectors API、更多CSS2選擇符和某些CSS3功能,還有一些HTML5的功能。不過IE9中的新功能無法使用。
- IE9:以IE9標準模式渲染頁面。IE9中的新功能都可以使用,比如ECMAScript5、完整的CSS3以及更多HTML5功能。這種文件模式是最高階的模式。
<meta http-equiv='X-UA-Compatible' content='IE=IEVersion'>
複製程式碼
The http-equiv attribute is used by servers to gather information about a page using the HTTP header. The meta tag’s http-equiv attribute set is similar to a http header.
children屬性
這個屬性是HTMLCollection的例項,只包含元素中同樣還是元素的子節點。除此之外,children屬性與childNodes沒有什麼區別。即在元素只包含元素子節點時,這兩個屬性的值相同。
contains方法
在實際開發中,經常需要知道某個節點是不是另一個節點的後代。IE為此率先引入了contains()方法,以便不通過在DOM文件樹中查詢即可獲得這個資訊。呼叫contains()方法的應該是祖先節點,也就是搜尋開始的節點,這個方法接收一個引數,即要檢測的後代節點。如果被檢測的節點是後代節點,該方法返回true;否則,返回false。
document.documentElement.contains(document.body); // true
複製程式碼
使用DOM Level3 compareDocumentPosition()也能夠確定節點間的關係。這個方法用於確定兩個節點間的關係,返回一個表示關係的位掩碼。
function contains (refNode, otherNode) {
if (typeof refNode.contains == 'function' && (!client.engine.webkit || client.engine.webkit >= 522)) {
return refNode.contains(otherNode);
} else if (typeof refNode.compareDocumentPosition == "function") {
return !!(refNode.compareDocumentPosition(otherNode) & 16);
} else {
var node = otherNode.parentNode;
do {
if(node === refNode) {
return true;
}
node = node.parentNode;
} while (node !== null);
return false;
}
}
複製程式碼
插入文字
前面介紹過,IE原來專有的插入標記的屬性innerHTML和outerHTML已經被HTML5納入規範。但另外兩個插入文字的專有屬性則沒有這麼好的運氣。這兩個沒有被HTML5看中的屬性是innerText和outerText。
innerText屬性
通過innerText屬性可以操作元素中包含的所有文字內容,包括子文件樹中的文字。在通過innerText讀取值時,它會按照由淺入深的順序,將子文件樹中的所有文字拼接起來。在通過innerText寫入值時,結果會刪除元素的所有子節點,插入包含相應文字值的文字節點。
支援innerText屬性的瀏覽器包括IE4+、safari 3+、Opera 8+和Chrome。Firefox雖然不支援innerText,但支援作用類似的textContent屬性。textContent是DOM Level3規定的一個屬性。為了確保跨瀏覽器相容,有必要編寫一個類似於下面的函式來檢測可以使用哪個屬性。
function getInnerText (element) {
return (typeof element.textContent == 'string') ? element.textContent : element.innerText;
}
function setInnerText (element, text) {
if (typeof element.textContent == 'string') {
element.textContent = text;
} else {
element.innerText = text;
}
}
複製程式碼
outerText屬性
除了作用範圍擴大到了包含呼叫它的節點之外,outerText與innerText基本上沒有多大區別。
div.outerText = 'Hello World';
// 這行程式碼實際上相當於如下兩行程式碼
var text = document.createTextNode('Hello World');
div.parentNode.replaceChild(text, div)
複製程式碼
滾動
HTML5之前的規範並沒有就與頁面滾動相關的API做出相關規定。但HTML5在將scrollIntoView()納入規範之後,仍然還有其他幾個專有方法可以在不同的瀏覽器中使用。下面列出的幾個方法都是對HTMLElement型別的擴充套件,因此在所有元素中都可以呼叫。
小結
本章主要介紹了一些DOM擴充套件:
- Selectors API,定義了兩個方法,讓開發人員能夠基於CSS選擇符從DOM中取得元素,這兩個方法是querySelector()和querySelectorAll()。
- Element Traversal,為DOM元素定義了額外的屬性,讓開發人員能夠更方便地從從一個元素跳到另一個元素。之所以會出現這個擴充套件,是因為瀏覽器處理DOM元素間空白符的方式不一樣。
- HTML5,為標準的DOM定義了很多擴充套件功能。
- 等等