JavaScript高級程序設計-DOM擴展

11 DOM擴展

內容

  • 理解 Selectors API
  • 使用 HTML5 DOM 擴展
  • 瞭解專有的 DOM 擴展

11.1 選擇符API

Selectors API( www.w3.org/TR/selectors-api/)是由 W3C 發起制定的一個標準,致力於讓瀏覽器原生支持 CSS 查詢。所有實現這一功能的 JavaScript 庫都會寫一個基礎的 CSS 解析器,然後再使用已有的DOM 方法查詢文檔並找到匹配的節點。核心方法

  • querySelector
  • querySelectorAll

11.1.1 querySelector()方法

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()方法時,只會在該元素後代元素的範圍內查找匹配的元素。

11.1.2 querySelector()方法

querySelector和querySelectorAll之間傳遞的參數類型,但是querySelectorAll返回的是包含屬性和方法的NodeList實例。

//取得某<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");

var i,len,strong;
for(i=0,len=strong.length;i<len;i++){
    strong = strongs[i];//或者strongs.item(i)
    strong.className = "important"
}

11.1.3matchesSelector()方法

接收一個參數,即 CSS 選擇符,如果調用元素與該選擇符匹配,返回 true;否則,返回 false。

if (document.body.matchesSelector("body.page1")){
    //true
}

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")){
//執行操作
}

11.2 元素遍歷

ElementTraversal規範爲DOM添加了一下5個屬性:

  • 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;
}

13.3 HTML5

對於傳統 HTML 而言, HTML5 是一個叛逆。所有之前的版本對 JavaScript 接口的描述都不過三言兩語,主要篇幅都用於定義標記,與 JavaScript 相關的內容一概交由 DOM 規範去定義。
而 HTML5 規範則圍繞如何使用新增標記定義了大量 JavaScript API。其中一些 API 與 DOM 重疊,定義了瀏覽器應該支持的 DOM 擴展。

11.3.1 與類有關的擴充

  1. getElementsByClassName()方法
    HTML5 添加的 getElementsByClassName()方法是最受人歡迎的一個方法,可以通過 document對象及所有 HTML 元素調用該方法。這個方法最早出現在 JavaScript 庫中,是通過既有的 DOM 功能實現的,而原生的實現具有極大的性能優勢
//取得所有類中包含"username"和"current"的元素,類名的先後順序無所謂
var allCurrentUsernames = document.getElementsByClassName("username current");
//取得 ID 爲"myDiv"的元素中帶有類名"selected"的所有元素
var selected = document.getElementById("myDiv").getElementsByClassName("selected");
  1. classList 屬性
    在操作類名時,需要通過 className 屬性添加、刪除和替換類名。因爲 className 中是一個字符串,所以即使只修改字符串一部分,也必須每次都設置整個字符串的值。
<div class="bd user disabled">...</div>

//首先,取得類名字符串並拆分成數組
var classNames = div.className.split("/\s+/");

//找到要刪的類名
var pos = -1,
    i,
    len;
    for(i = 0;len;className.length;i<len;i++){
        if(className[i]=="user"){
            pos = i;
            break;
        }
    }
//刪除類名
classNames.splice(i,1);
//把剩下的類名拼成字符串並重新設置
div.className = classNames.join(" ");

HTML5 新增了一種操作類名的方式,可以讓操作更簡單也更安全,那就是爲所有元素添加classList 屬性。這個 classList 屬性是新集合類型 DOMTokenList 的實例:

  • add(value):將給定的字符串值添加到列表中。如果值已經存在,就不添加了。
  • contains(value):表示列表中是否存在給定的值,如果存在則返回 true,否則返回false。
  • remove(value):從列表中刪除給定的字符串。
  • toggle(value):如果列表中已經存在給定的值,刪除它;如果列表中沒有給定的值,添加它。
    這樣,前面那麼多行代碼用下面這一行代碼就可以代替了:
div.classList.remove("user");

//刪除"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]);
}

11.3.2 焦點管理

HTML5 也添加了輔助管理 DOM 焦點的功能。首先就是 document.activeElement 屬性,這個
屬性始終會引用 DOM 中當前獲得了焦點的元素。元素獲得焦點的方式有頁面加載、用戶輸入(通常是通過按 Tab 鍵)和在代碼中調用 focus()方法

var button = document.getElementById("myButton");
button.focus();
alert(docuemnt.activeElement===button);  //true
//文檔加載完成 document.activeElement保存的是document.body元素的引用
//文檔加載期間 document。activeElement值爲null

增加了document.hasFocus,用於確定文檔是否獲得焦點

var button = document.getElementById("myButton");
button.focus();
alert(document.hasFocus()); //true

11.3.3HTMLDocument變化

HTML5 擴展了 HTMLDocument,增加了新的功能。與 HTML5 中新增的其他 DOM 擴展類似,這些變化同樣基於那些已經得到很多瀏覽器完美支持的專有擴展。

  1. readyState屬性
    IE4 最早爲 document 對象引入了 readyState 屬性。然後,其他瀏覽器也都陸續添加這個屬性,最終 HTML5 把這個屬性納入了標準當中。
  • loading,正在加載文檔;
  • complete,已經加載完文檔
if(document.readyState=="complete"){
    //執行操作
}
  1. 兼容模式
    IE6開始區分頁面渲染模式,標準模式/混雜模式 compatMode
if (document.compatMode == "CSS1Compat"){
    alert("Standards mode");
} else {
    alert("Quirks mode");
}
  1. head屬性
    HTML5 新增了 document.head 屬性,引用文檔的/元素。
var head = document.head || document.getElementsByTagName("head")[0];

11.3.4 字符集屬性

alert(document.charset); //UTF-16
document.charset = "UTF-8";

另一個屬性是 defaultCharset,表示根據默認瀏覽器及操作系統的設置,當前文檔默認的字符集應該是什麼

if(document.charset!=document.defaultCharset){
    alert("custom charset set being used")
}

11.3.5 自定義數據屬性

HTML5 規定可以爲元素添加非標準的屬性,但要添加前綴 data-,目的是爲元素提供與渲染無關的信息,或者提供語義信息。

<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>

添加了自定義屬性之後,可以通過元素的 dataset 屬性來訪問自定義屬性的值。 dataset 屬性的值是 DOMStringMap 的一個實例,也就是一個名值對兒的映射。

//本例中使用的方法僅用於演示
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);
}

11.3.6 插入標記

  1. innerHTML
    在讀模式下, innerHTML 屬性返回與調用元素的所有子節點(包括元素、註釋和文本節點)對應的 HTML 標記。在寫模式下, innerHTML 會根據指定的值創建新的 DOM 樹,然後用這個 DOM 樹完全替換調用元素原先的所有子節點。
<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>
  1. outerHTML屬性
    在讀模式下, outerHTML 返回調用它的元素及所有子節點的 HTML 標籤。 在寫模式下, 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);
  1. insertAdjacentHTML()方法
    插入標記的最後一個新增方式是 insertAdjacentHTML()方法。這個方法最早也是在IE中出現的,它接收兩個參數:插入位置和要插入的 HTML 文本。
  • “beforebegin”,在當前元素之前插入一個緊鄰的同輩元素;
  • “afterbegin”, 在當前元素之下插入一個新的子元素或在第一個子元素之前再插入新的子元素;
  • “beforeend”, 在當前元素之下插入一個新的子元素或在最後一個子元素之後再插入新的子元素;
  • “afterend”,在當前元素之後插入一個緊鄰的同輩元素。
//作爲前一個同輩元素插入
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>");
  1. 內存與性能問題
    在刪除帶有事件處理程序或引用了其他 JavaScript 對象子樹時,就有可能導致內存佔用問題。假設某個元素有一個事件處理程序(或者引用了一個 JavaScript 對象作爲屬性),在使用前述某個屬性將該元素從文檔樹中刪除後,元素與事件處理程序(或 JavaScript 對象)之間的綁定關係在內存中並沒有一併刪除。如果這種情況頻繁出現,頁面佔用的內存數量就會明顯增加。因此,在使用 innerHTML、outerHTML 屬性和 insertAdjacentHTML()方法時,最好先手工刪除要被替換的元素的所有事件處理程序和 JavaScript 對象屬性。
    效率低的方法:
for(var i=0,len = values.length;i<len;i++){
    ul.innerHTML +="<li>"+values[i]+"</li>";//避免此種操作
}

改進效率後的辦法:

var itemsHTML = "";
for(var i=0,len = values.length;i<len;i++){
    itemHTML +="<li>"+values[i]+"</li>";
}
ul.innerHTML = itemsHTML;

11.3.7 scrollIntoView

scrollIntoView()可以在所有 HTML 元素上調用,通過滾動瀏覽器窗口或某個容器元素,調用
元素就可以出現在視口中。如果給這個方法傳入 true 作爲參數,或者不傳入任何參數,那麼窗口滾動之後會讓調用元素的頂部與視口頂部儘可能平齊。如果傳入 false 作爲參數,調用元素會盡可能全部出現在視口中.

//讓元素可見
document.forms[0].scrollIntoView();

11.4 專有擴展

11.4.1 文檔標準

IE8 引入了一個新的概念叫“文檔模式”( document mode)。

  • IE5:以混雜模式渲染頁面( IE5 的默認模式就是混雜模式)。 IE8 及更高版本中的新功能都無法使用。
  • IE7:以 IE7 標準模式渲染頁面。 IE8 及更高版本中的新功能都無法使用。
  • IE8:以 IE8 標準模式渲染頁面。 IE8 中的新功能都可以使用,因此可以使用 Selectors API、更多CSS2 級選擇符和某些 CSS3 功能,還有一些 HTML5 的功能。不過 IE9 中的新功能無法使用。
  • IE9:以 IE9 標準模式渲染頁面。 IE9 中的新功能都可以使用,比如 ECMAScript 5、完整的 CSS3以及更多 HTML5 功能。這個文檔模式是最高級的模式。

11.4.2 children屬性

這個屬性是 HTMLCollection 的實例,只包含元素中同樣還是元素的子節點。

11.4.3 contains方法

IE 爲此率先引入了 contains()
方法,以便不通過在 DOM 文檔樹中查找即可獲得這個信息。調用 contains()方法的應該是祖先節點,也就是搜索開始的節點,這個方法接收一個參數,即要檢測的後代節點。

alert(document.documentElement.contains(document.body)); //true

使用 DOM Level 3 compareDocumentPosition()也能夠確定節點間的關係。

function contains(refNode,otherNode){
    if(typeof refNode.contains == "function"&&(!client.engine.webkit||client.engine.webkit>=522)){
        return ref.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{
                ndoe = node.parentNode;
            }
        }while(node!==null);
        return false;
    }
}

11.4.4 插入文本

  1. innerText屬性
    通過 innertText 屬性可以操作元素中包含的所有文本內容,包括子文檔樹中的文本。在通過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特性檢查

function getInnerText(element){
    return (typeof element.textContent == "string")?element.textContent:element.innerText;
}

function setInnerText(element)
if(typeof element.textContent == "string"){
    element.textContent = text;
}else{
    element.innerText = text;
}

實際上, innerText 與 textContent 返回的內容並不完全一樣。比如,innerText 會忽略行內的樣式和腳本,而 textContent 則會像返回其他文本一樣返回行內的樣式和腳本代碼。避免跨瀏覽器兼容問題的最佳途徑,就是從不包含行內樣式或行內腳本的 DOM 子樹副本或 DOM 片段中讀取文本。

  1. outText屬性
    在讀取文本值時, outerText 與 innerText 的結果完全一樣。但在寫模式下, outerText 就完全不同了: outerText 不只是替換調用它的元素的子節點,而是會替換整個元素
div.outerText = "Hello world!";
//這行代碼實際上相當於如下兩行代碼:
var text = document.createTextNode("Hello world!");
div.parentNode.replaceChild(text, div);

11.4.5 滾動

  • scrollIntoViewIfNeeded(alignCenter):只在當前元素在視口中不可見的情況下,才滾動瀏覽器窗口或容器元素,最終讓它可見。如果當前元素在視口中可見,這個方法什麼也不做。如果將可選的 alignCenter 參數設置爲 true,則表示儘量將元素顯示在視口中部(垂直方向)。Safari 和 Chrome 實現了這個方法。
  • scrollByLines(lineCount):將元素的內容滾動指定的行高, lineCount 值可以是正值,也可以是負值。 Safari 和 Chrome 實現了這個方法。
  • scrollByPages(pageCount):將元素的內容滾動指定的頁面高度,具體高度由元素的高度決定。 Safari 和 Chrome 實現了這個方法。
//將頁面主體滾動 5 行
document.body.scrollByLines(5);
//在當前元素不可見的時候,讓它進入瀏覽器的視口
document.images[0].scrollIntoViewIfNeeded();
//將頁面主體往回滾動 1 頁
document.body.scrollByPages(-1);

11.5 小結

本章介紹的三個這方面的規範如下。

  • Selectors API,定義了兩個方法,讓開發人員能夠基於 CSS 選擇符從 DOM 中取得元素,這兩個
    方法是 querySelector()和 querySelectorAll()。
  • Element Traversal,爲 DOM 元素定義了額外的屬性,讓開發人員能夠更方便地從一個元素跳到
    另一個元素。之所以會出現這個擴展,是因爲瀏覽器處理 DOM 元素間空白符的方式不一樣。
  • HTML5,爲標準的 DOM 定義了很多擴展功能。其中包括在 innerHTML 屬性這樣的事實標準基
    礎上提供的標準定義,以及爲管理焦點、設置字符集、滾動頁面而規定的擴展 API。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章