DOM擴展
1、選擇符API
querySelector()方法
接收一個 CSS 選擇符,返回與該模式匹配的第一個元素,如果沒有找到匹配的元素,返回 null
//取得 body 元素 var body = document.querySelector("body"); //取得 ID 為"myDiv"的元素 var myDiv = document.querySelector("#myDiv"); //取得類為"selected"的第一個元素 var selected = document.querySelector(".selected"); //取得類為"button"的第一個圖像元素 var img = document.body.querySelector("img.button");
通過 Document 類型調用 querySelector()方法時,會在文檔元素的範圍內查找匹配的元素。而通過 Element 類型調用 querySelector()方法時,只會在該元素後代元素的範圍內查找匹配的元素
如果傳入了不被支持的選擇符,querySelector()會拋出錯誤
querySelectorAll()方法
接收一個 CSS 選擇符,返回的一個 NodeList 的實例,包含匹配的所有元素,如果沒有找到匹配的元素,NodeList 就是空的
能夠調用 querySelectorAll()方法的類型包括 Document、DocumentFragment 和 Element
//取得某<div>中的所有<em>元素(類似於 getElementsByTagName("em")) var ems = document.getElementById("myDiv").querySelectorAll("em"); //取得類為"selected"的所有元素 var selecteds = document.querySelectorAll(".selected"); //取得所有<p>元素中的所有<strong>元素 var strongs = document.querySelectorAll("p strong");
要取得返回的 NodeList 中的每一個元素,可以使用 item()方法,也可以使用方括號語法
var i, len, strong; for (i=0, len=strongs.length; i < len; i++){ strong = strongs[i]; //或者 strongs.item(i) strong.className = "important"; }
如果傳入了瀏覽器不支持的選擇符或者選擇符中有語法錯誤,querySelectorAll()會拋出錯誤
matchesSelector()方法
接收一個參數,即 CSS 選擇符,如果調用元素與該選擇符匹配,返回 true;否則,返回 false
if (document.body.matchesSelector("body.page1")){ //true }
兼容性:
- IE 9+通過 msMatchesSelector()支持該方法
- Firefox 3.6+通過 mozMatchesSelector()支持該方法
- Safari 5+和 Chrome 通過 webkitMatchesSelector()支持該方法
function matchesSelector(element, selector){ if (element.matchesSelector){ return element.matchesSelector(selector); } else if (element.msMatchesSelector){ return element.msMatchesSelector(selector); } else if (element.mozMatchesSelector){ return element.mozMatchesSelector(selector); } else if (element.webkitMatchesSelector){ return element.webkitMatchesSelector(selector); } else { throw new Error("Not supported."); } } if (matchesSelector(document.body, "body.page1")){ //執行操作 }
2、元素遍歷
childElementCount:返回子元素(不包括文本節點和註釋)的個數
firstElementChild:指向第一個子元素;firstChild 的元素版
lastElementChild:指向最後一個子元素;lastChild 的元素版
previousElementSibling:指向前一個同輩元素;previousSibling 的元素版
nextElementSibling:指向後一個同輩元素;nextSibling 的元素版
//跨瀏覽器遍歷某元素的所有子元素的一般方法 var i, len, child = element.firstChild; while(child != element.lastChild){ if (child.nodeType == 1){ //檢查是不是元素 processChild(child); } child = child.nextSibling; } //而使用 Element Traversal 新增的元素 var i, len, child = element.firstElementChild; while(child != element.lastElementChild){ processChild(child); //已知其是元素 child = child.nextElementSibling; }
3、HTML5
與類相關的擴充
getElementsByClassName()方法:接收一個參數,即一個包含一或多個類名的字符串,返回帶有指定類的所有元素的 NodeList
//取得所有類中包含"username"和"current"的元素,類名的先後順序無所謂 var allCurrentUsernames = document.getElementsByClassName("username current"); //取得 ID 為"myDiv"的元素中帶有類名"selected"的所有元素 var selected = document.getElementById("myDiv").getElementsByClassName("selected");
classList 屬性
概念:一種操作類名的方式,可以讓操作更簡單也更安全,那就是為所有元素添加classList 屬性。這個 classList 屬性是新集合類型 DOMTokenList 的實例。與其他 DOM 集合類似,DOMTokenList 有一個表示自己包含多少元素的 length 屬性,而要取得每個元素可以使用 item()方法,也可以使用方括號語法
方法:
add(value):將給定的字符串值添加到列表中。如果值已經存在,就不添加了 contains(value):表示列表中是否存在給定的值,如果存在則返回 true,否則返回 false remove(value):從列表中刪除給定的字符串 toggle(value):如果列表中已經存在給定的值,刪除它;如果列表中沒有給定的值,添加它
有了 classList 屬性,除非你需要全部刪除所有類名,或者完全重寫元素的 class 屬性,否則也
就用不到 className 屬性了//<div class="bd user disabled">...</div> //刪除"disabled"類 div.classList.remove("disabled"); //添加"current"類 div.classList.add("current"); //切換"user"類 div.classList.toggle("user"); //確定元素中是否包含既定的類名 if (div.classList.contains("bd") && !div.classList.contains("disabled")){ //執行操作 ) //叠代類名 for (var i=0, len=div.classList.length; i < len; i++){ doSomething(div.classList[i]); }
焦點管理
document.activeElement 屬性:始終會引用 DOM 中當前獲得了焦點的元素。元素獲得焦點的方式有頁面加載、用戶輸入(通常是通過按 Tab 鍵)和在代碼中調用 focus()方法
var button = document.getElementById("myButton"); button.focus(); alert(document.activeElement === button); //true
document.hasFocus()方法:用於確定文檔是否獲得了焦點
var button = document.getElementById("myButton"); button.focus(); alert(document.hasFocus()); //true
HTMLDocument的變化
Document 的readyState 屬性:
- loading,正在加載文檔;
- complete,已經加載完文檔
if (document.readyState == "complete"){ //執行操作 }
兼容模式:document 添加了一個名為 compatMode 的屬性,為了告訴開發人員瀏覽器采用了哪種渲染模式
if (document.compatMode == "CSS1Compat"){ alert("Standards mode");//標準模式下,值等於"CSS1Compat" } else { alert("Quirks mode");//混雜模式下,值等於"BackCompat"。 }
head 屬性:作為對 document.body 引用文檔的元素的補充,引用文檔的元素
var head = document.head || document.getElementsByTagName("head")[0];
字符集屬性
charset 屬性:表示文檔中實際使用的字符集,也可以用來指定新字符集。默認情況下,這個屬性的值為"UTF-16",但可以通過元素、響應頭部或直接設置 charset 屬性修改這個值
alert(document.charset); //"UTF-16" document.charset = "UTF-8";
defaultCharset屬性:表示根據默認瀏覽器及操作系統的設置,當前文檔默認的字符集應該是什麽。如果文檔沒有使用默認的字符集,那 charset 和 defaultCharset 屬性的值可能會不一樣
if (document.charset != document.defaultCharset){ alert("Custom character set being used."); }
自定義數據屬性
HTML5 規定可以為元素添加非標準的屬性,但要添加前綴 data-,這些屬性可以任意添加、隨便命名,只要以 data-開頭即可
<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>
添加了自定義屬性之後,可以通過元素的 dataset 屬性來訪問自定義屬性的值。dataset 屬性的值是 DOMStringMap 的一個實例,也就是一個名值對兒的映射。在這個映射中,每個 data-name 形式
的屬性都會有一個對應的屬性,只不過屬性名沒有 data-前綴(比如,自定義屬性是 data-myname,
那映射中對應的屬性就是 myname)//本例中使用的方法僅用於演示 var div = document.getElementById("myDiv"); //取得自定義屬性的值 var appId = div.dataset.appId; var myName = div.dataset.myname; //設置值 div.dataset.appId = 23456; div.dataset.myname = "Michael"; //有沒有"myname"值呢? if (div.dataset.myname){ alert("Hello, " + div.dataset.myname); }
插入標記
innerHTML 屬性
在讀模式下,innerHTML 屬性返回與調用元素的所有子節點(包括元素、註釋和文本節點)對應的 HTML 標記;
<div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div> 對於上面的<div>元素來說,它的 innerHTML 屬性會返回如下字符串 <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul>
在寫模式下,innerHTML 會根據指定的值創建新的 DOM 樹,然後用這個 DOM 樹完全替換調用元素原先的所有子節點
div.innerHTML = "Hello world!"; div.innerHTML = "Hello & welcome, <b>\"reader\"!</b>"; //結果: //<div id="content">Hello & welcome, <b>"reader"!</b></div>
不同瀏覽器返回的文本格式會有所不同。IE 和 Opera 會將所有標簽轉換為大寫形式,而 Safari、Chrome 和 Firefox 則會原原本本地按照原先文檔中(或指定這些標簽時)的格式返回 HTML,包空格和縮進
在大多數瀏覽器中,通過 innerHTML 插入
<script>
元素並不會執行其中的腳本div.innerHTML = "<script defer>alert(‘hi‘);<\/script>"; //無效 //下面這幾行代碼都可以正常執行 div.innerHTML="_<script defer>alert(‘hi‘);<\/script>"; div.innerHTML="<div> </div><script defer>alert(‘hi‘);<\/script>"; div.innerHTML="<input type=\"hidden\"><script defer>alert(‘hi‘)<\/script>";
大多數瀏覽器都支持以直觀的方式通過 innerHTML 插入
<style>
元素div.innerHTML="<style type=\"text/css\">body{background-color:red;</style>" //IE8 及更早版本中 div.innerHTML="_<style type=\"text/css\">body{background-color:red;</style>" div.removeChild(div.firstChild);
並不是所有元素都支持 innerHTML 屬性。不支持 innerHTML 的元素有:
<col>、<colgroup>、<frameset>、<head>、<html>、<style>、<table>、<tbody>、<thead>、<tfoot>和<tr>
無論什麽時候,只要使用 innerHTML 從外部插入 HTML,都應該首先以可靠的方式處理 HTML。IE8 為此提供了 window.toStaticHTML()方法,這個方法接收一個參數,即一個 HTML 字符串;返回一個經過無害處理後的版本——從源 HTML 中刪除所有腳本節點和事件處理程序屬性
var text = "<a href=\"#\" onclick=\"alert(‘hi‘)\">Click Me</a>"; var sanitized = window.toStaticHTML(text); //Internet Explorer 8 only alert(sanitized); //"<a href=\"#\">Click Me</a>"
outerHTML 屬性
在讀模式下,outerHTML 返回調用它的元素及所有子節點的 HTML 標簽
<div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div> 對於上面的<div>元素來說,它的 outerHTML 屬性會返回如下字符串 <div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div>
在寫模式下,outerHTML會根據指定的 HTML 字符串創建新的 DOM 子樹,然後用這個 DOM 子樹完全替換調用元素。
div.outerHTML = "<p>This is a paragraph.</p>"; //這行代碼完成的操作與下面這些 DOM 腳本代碼一樣 var p = document.createElement("p"); p.appendChild(document.createTextNode("This is a paragraph.")); div.parentNode.replaceChild(p, div);
insertAdjacentHTML()方法:它接收兩個參數,插入位置和要插入的 HTML 文本
第一個參數必須是下列值之一(這些值都必須是小寫形式):
"beforebegin",在當前元素之前插入一個緊鄰的同輩元素; "afterbegin",在當前元素之下插入一個新的子元素或在第一個子元素之前再插入新的子元素; "beforeend",在當前元素之下插入一個新的子元素或在最後一個子元素之後再插入新的子元素; "afterend",在當前元素之後插入一個緊鄰的同輩元素。
第二個參數是一個 HTML 字符串(與 innerHTML 和 outerHTML的值相同),如果瀏覽器無法解析該字符串,就會拋出錯誤
//作為前一個同輩元素插入 element.insertAdjacentHTML("beforebegin", "<p>Hello world!</p>"); //作為第一個子元素插入 element.insertAdjacentHTML("afterbegin", "<p>Hello world!</p>"); //作為最後一個子元素插入 element.insertAdjacentHTML("beforeend", "<p>Hello world!</p>"); //作為後一個同輩元素插入 element.insertAdjacentHTML("afterend", "<p>Hello world!</p>");
內存與性能問題
在使用 innerHTML、outerHTML 屬性和 insertAdjacentHTML()方法時,最好先手工刪除要被替換的元素的所有事件處理程序和 JavaScript 對象屬性
創建和銷毀 HTML 解析器也會帶來性能損失,所以最好能夠將設置 innerHTML或 outerHTML 的次數控制在合理的範圍內
var itemsHtml = ""; for (var i=0, len=values.length; i < len; i++){ itemsHtml += "<li>" + values[i] + "</li>"; } ul.innerHTML = itemsHtml;
scrollIntoView()方法
- scrollIntoView()可以在所有 HTML 元素上調用,通過滾動瀏覽器窗口或某個容器元素,調用元素就可以出現在視口中
- 如果給這個方法傳入 true 作為參數,或者不傳入任何參數,那麽窗口滾動之後會讓調用元素的頂部與視口頂部盡可能平齊
- 如果傳入 false 作為參數,調用元素會盡可能全部出現在視口中,(可能的話,調用元素的底部會與視口頂部平齊。)不過頂部不一定平齊
//讓元素可見 document.forms[0].scrollIntoView();
4、專有擴展
文檔模式
文檔模式決定了你可以使用哪個級別的 CSS,可以在 JavaScript 中使用哪些 API,以及如何對待文檔類型(doctype)
要強制瀏覽器以某種模式渲染頁面,可以使用 HTTP 頭部信息X-UA-Compatible,或通過等價的標簽來設置
<meta http-equiv="X-UA-Compatible" content="IE=IEVersion">
默認情況下,瀏覽器會通過文檔類型聲明來確定是使用最佳的可用文檔模式,還是使用混雜模式,通過 document.documentMode 屬性可以知道給定頁面使用的是什麽文檔模式
var mode = document.documentMode;
children屬性
由於 IE9 之前的版本與其他瀏覽器在處理文本節點中的空白符時有差異,因此就出現了 children屬性
這個屬性是 HTMLCollection 的實例,只包含元素中同樣還是元素的子節點
除此之外,children 屬性與 childNodes 沒有什麽區別,即在元素只包含元素子節點時,這兩個屬性的值相同
var childCount = element.children.length; var firstChild = element.children[0];
contains()方法
調用 contains()方法的應該是祖先節點,也就是搜索開始的節點,這個方法接收一個參數,即要檢測的後代節點。如果被檢測的節點是後代節點,該方法返回 true;否則,返回 false
alert(document.documentElement.contains(document.body)); //true
compareDocumentPosition()也能夠確定節點間的關系,這個方法用於確定兩個節點間的關系,返回一個表示該關系的位掩碼( bitmask)
var result = document.documentElement.compareDocumentPosition(document.body); alert(!!(result & 16)); //16代表節點被包含(給定的節點是參考節點的後代)
兼容性寫法
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; } else { node = node.parentNode; } } while (node !== null); return false; } }
- 插入文本
innerText 屬性:可以操作元素中包含的所有文本內容,包括子文檔樹中的文本
在通過innerText 讀取值時,它會按照由淺入深的順序,將子文檔樹中的所有文本拼接起來
<div id="content"> <p>This is a <strong>paragraph</strong> with a list following it.</p> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div> 對於這個例子中的<div>元素而言,其 innerText 屬性會返回下列字符串: This is a paragraph with a list following it. Item 1 Item 2 Item 3
在通過innerText 寫入值時,結果會刪除元素的所有子節點,插入包含相應文本值的文本節點;也對文本中存在的 HTML 語法字符(小於號、大於號、引號及和號)進行了編碼
div.innerText = "Hello & welcome, <b>\"reader\"!</b>"; 運行以上代碼之後,會得到如下所示的結果 <div id="content"> Hello &welcome,<b>"reader"!</b> </div>
設置 innerText 永遠只會生成當前節點的一個子文本節點,而為了確保只生成一個子文本節點,就必須要對文本進行 HTML 編碼。利用這一點,可以通過 innerText 屬性過濾掉 HTML 標簽。方法是將 innerText 設置為等於 innerText,這樣就可以去掉所有 HTML 標簽
div.innerText = div.innerText;
由於不同瀏覽器處理空白符的方式不同,因此輸出的文本可能會也可能不會包含原始 HTML 代碼中的縮進
兼容性:
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; } } setInnerText(div, "Hello world!"); alert(getInnerText(div)); //"Hello world!"
outerText 屬性
在讀取文本值時,outerText 與 innerText 的結果完全一樣。但在寫模式下,outerText 就完全不
同了:outerText 不只是替換調用它的元素的子節點,而是會替換整個元素(包括子節點)div.outerText = "Hello world!"; //這行代碼實際上相當於如下兩行代碼: var text = document.createTextNode("Hello world!"); div.parentNode.replaceChild(text, div);
本質上,新的文本節點會完全取代調用 outerText 的元素。此後,該元素就從文檔中被刪除,無法訪問
滾動
- 是對 HTMLElement 類型的擴展,因此在所有元素中都可以調用:
- scrollIntoViewIfNeeded(alignCenter):只在當前元素在視口中不可見的情況下,才滾動瀏覽器窗口或容器元素,最終讓它可見。如果當前元素在視口中可見,這個方法什麽也不做。如果將可選的 alignCenter 參數設置為 true,則表示盡量將元素顯示在視口中部(垂直方向)。Safari 和 Chrome 實現了這個方法
- scrollByLines(lineCount):將元素的內容滾動指定的行高,lineCount 值可以是正值,也可以是負值。Safari 和 Chrome 實現了這個方法
- scrollByPages(pageCount):將元素的內容滾動指定的頁面高度,具體高度由元素的高度決定。Safari 和 Chrome 實現了這個方法
scrollIntoView()和 scrollIntoViewIfNeeded()的作用對象是元素的容器,而 scrollByLines()和scrollByPages()影響的則是元素自身
//將頁面主體滾動 5 行 document.body.scrollByLines(5); //在當前元素不可見的時候,讓它進入瀏覽器的視口 document.images[0].scrollIntoViewIfNeeded(); //將頁面主體往回滾動 1 頁 document.body.scrollByPages(-1); 由於 scrollIntoView()是唯一一個所有瀏覽器都支持的方法,因此還是這個方法最常用。
- 是對 HTMLElement 類型的擴展,因此在所有元素中都可以調用:
5、總結
- 雖然 DOM 為與 XML 及 HTML 文檔交互制定了一系列核心 API,但仍然有幾個規範對標準的 DOM進行了擴展。這些擴展中有很多原來是瀏覽器專有的,但後來成為了事實標準,於是其他瀏覽器也都提供了相同的實現。本章介紹的三個這方面的規範如下:
- Selectors API,定義了兩個方法,讓開發人員能夠基於 CSS 選擇符從 DOM 中取得元素,這兩個方法是 querySelector()和 querySelectorAll()
- Element Traversal,為 DOM 元素定義了額外的屬性,讓開發人員能夠更方便地從一個元素跳到另一個元素。之所以會出現這個擴展,是因為瀏覽器處理 DOM 元素間空白符的方式不一樣
- HTML5,為標準的 DOM 定義了很多擴展功能。其中包括在 innerHTML 屬性這樣的事實標準基礎上提供的標準定義,以及為管理焦點、設置字符集、滾動頁面而規定的擴展 API
- 雖然目前 DOM 擴展的數量還不多,但隨著 Web 技術的發展,相信一定還會湧現出更多擴展來。很多瀏覽器都在試驗專有的擴展,而這些擴展一旦獲得認可,就能成為“偽”標準,甚至會被收錄到規範的更新版本中
DOM擴展