1. 程式人生 > >js學習筆記----JavaScript中DOM擴充套件的那些事

js學習筆記----JavaScript中DOM擴充套件的那些事

什麼都不說,先上總結的圖~

Selectors API(選擇符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()時,只會在該元素後代元素的範圍內查詢匹配的元素.

如果傳入不被支援的css選擇符,querySelector()會丟擲錯誤.

querySelectorAll()方法

querySelectorAll()引數也是一個CSS選擇符,返回的是所匹配的元素而不是僅僅一個元素,這個方法返回的是一個NodeList例項.如果沒有找到匹配的元素,NodeList就是空的.

具體的說,返回的值實際上是帶有所有屬性和方法的NodeList,而底層實現則類似於一組元素的快照,而非不斷對文件進行搜尋的動態查詢.這樣可以避免使用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");

matchesSelector()方法

Selectors API Level 2規範為Element型別新增了這個方法,該方法接收一個引數,即CSS選擇符,如果呼叫元素和該選擇符匹配,返回true;否則返回false

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

在取得某個元素引用的情況下,使用這個方法能夠方便的檢測它是否會被querySelector()或querySelectorAll()方法返回.

元素遍歷

對於元素間的空格, IE9 及之前版本不會返回文字節點,而其他所有瀏覽器都會返回文字節點。這樣,就導致了在使用 childNodes 和 firstChild 等屬性時的行為不一致。為了彌補這一差異,而同時又保持 DOM規範不變,Element Traversal 規範新定義了一組屬性。 Element Traversal API 為 DOM 元素添加了以下 5 個屬性。

  • childElementCount :返回子元素(不包括文字節點和註釋)的個數。
  • firstElementChild :指向第一個子元素; firstChild 的元素版。
  • lastElementChild :指向最後一個子元素; lastChild 的元素版。
  • previousElementSibling :指向前一個同輩元素; previousSibling 的元素版。
  • nextElementSibling :指向後一個同輩元素; nextSibling 的元素版。

支援的瀏覽器為DOM元素新增這些屬性,利用這些元素不必擔心空白文字節點,可以更方便的查詢DOM元素了.如下例子:

之前實現跨瀏覽器遍歷某元素的所有子元素,需要這樣寫:

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

HTML5

與類相關的擴充

getElementsByClassName()方法

通過document物件及所有HTML元素呼叫該方法.該方法接收一個引數,即一個包含一個或多個類名的字串,返回帶有指定類的所有元素的NodeList.傳入多個類的時候,類名的先後順序不重要.

//取得所有類中包含"username"和"current"的元素,類名的先後順序無所謂
var allCurrentUsernames = document.getElementsByClassName("username current");

//取得 ID 為"myDiv"的元素中帶有類名"selected"的所有元素
var selected = document.getElementById("myDiv").getElementsByClassName("selected");

使用這個方法課可以更方便的為帶有某些類的元素新增事件處理程式,從而不侷限於ID或者標籤名,需要注意的是,因為返回的物件是NodeList,所以這個方法與使用getElementsByTagName()以及其他返回NodeList的DOM方法都具有同樣的效能問題.

classList屬性

在操作類名時通過className屬性新增刪除或者替換類名,因為className是一個字串,所以即使修改字串的一部分,也必須每次都設定整個字串的值.

//刪除"user"類
//首先,取得類名字串並拆分成陣列
var classNames = div.className.split(/\s+/);
//找到要刪的類名
var pos = -1,
i,
len;
for (i=0, len=classNames.length; i < len; i++){
if (classNames[i] == "user"){
pos = i;
break;
}
}
//刪除類名
classNames.splice(i,1);
//把剩下的類名拼成字串並重新設定
div.className = classNames.join(" ");

這種處理className的方式是低效且繁瑣的,HTML5中新增了一種操作類名的方式,就是為所有元素新增classList屬性,這個classList屬性是新集合型別DOMTokenList的例項.這個新型別還定義瞭如下方法:

  • add(value) :將給定的字串值新增到列表中。如果值已經存在,就不添加了。
  • contains(value) :表示列表中是否存在給定的值,如果存在則返回 true ,否則返回 false 。
  • remove(value) :從列表中刪除給定的字串。
  • toggle(value) :如果列表中已經存在給定的值,刪除它;如果列表中沒有給定的值,新增它。
//刪除"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.activeElement中儲存的是document.body元素的引用.文件在載入期間,document.activeElement的值為null.

document.hasFocus()方法

這個方法用於確定文件是否獲得了焦點,通過檢測檔案是否獲得了焦點,可以獲得使用者是否正在與頁面互動

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

HTMLDocument的擴充

readyState屬性

Document的readyState屬性有兩個可能的值:

  • loading ,正在載入文件;
  • complete ,已經載入完文件。

 通過document.readyState來實現一個指示文件已經載入完成的指示器.在這之前,我們想要實現一個指示器,必須藉助onload之間處理程式設定一個標籤,表明文件已經載入完畢,document.readyState屬性的基本用法如下:

if (document.readyState == "complete"){
//執行操作
}

相容模式

IE6開始區分渲染頁面的模式是標準的還是混雜的,檢測頁面的相容模式就成為瀏覽器的必要功能.IE為此給document添加了一個名為compatMode的屬性,這個屬性就是為了告訴開發人員瀏覽器採用哪種渲染模式.在標準模式下,document.compatMode的值為"CSS1Compat",而在混雜模式下,document.compatMode的值等於"BackCompat".

if (document.compatMode == "CSS1Compat"){
alert("Standards mode");
} else {
alert("Quirks mode");
}

head屬性

HTML5新增了document.head屬性,用來引用文件的<head>元素,如果要引用文件的<head>元素,可以結合使用這個屬性和另一種後備方法

var head = document.head || document.getElementsByTagName("head")[0];

字符集屬性

charset屬性

charset屬性表示文件中實際使用的字符集,也可以用來指定新字符集.預設情況下這個屬性的值是"UTF-16",可以通過<meta>元素,響應頭部或直接設定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屬性來訪問自定義屬性的值.dataset屬性的值是DOMStringMap的一個例項,也就是一個名值對的對映.在對映中,每個data-name形式的睡醒都會有一個對應的屬性,只不過屬性名沒有data-字首.如下例所示:

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

如果需要給元素新增一些不可見的資料以便進行其他處理,那就要用到自定義資料屬性。在跟蹤連結或混搭應用中,通過自定義資料屬效能方便地知道點選來自頁面中的哪個部分。

插入標記

通過使用插入標記的技術,直接插入HTML字串不僅更簡單,速度也更快.以下與插入標記相關的DOM擴充套件已經納入了HTML5規範.

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
<ul>
<li>Item
<li>Item
<li>Item
</ul>

需要注意的是,不同瀏覽器返回的文字格式會有所不同,所以不要指望所有瀏覽器返回的innerHTML值完全相同.

在寫模式下,innerHTML的值會被解析為DOM子樹,替換呼叫元素原來所有的子節點,因為它的值被認為是HTML,所以其中的所有標籤都會按照瀏覽器處理HTML的標準方式轉換為元素,如果設定的值僅是文字而沒有HTML標籤,那麼結果就是設定純文字.

為 innerHTML 設定 HTML 字串後,瀏覽器會將這個字串解析為相應的 DOM樹。因此設定了 innerHTML 之後,再從中讀取 HTML 字串,會得到與設定時不一樣的結果。原因在於返回的字串是根據原始 HTML 字串建立的 DOM 樹經過序列 化之後的結果。

使用 innerHTML 屬性也有一些限制。比如,在大多數瀏覽器中,通過 innerHTML 插入 <script>元素並不會執行其中的指令碼。

大多數瀏覽器都支援以直觀的方式通過 innerHTML 插入 <style> 元素,如下所示:

div.innerHTML = "<style type=\"text/css\">body {background-color: red; }</style>";

還需要注意的是,並不是所有的元素都支援innerHTML屬性,不支援innerHTML的元素有:<col> 、 <colgroup> 、<frameset> 、 <head> 、 <html> 、 <style> 、 <table> 、 <tbody> 、 <thead> 、 <tfoot> 和 <tr> 。

所以無論什麼時候,只要使用innerHTML從外部插入HTML,都應該首先以可靠的方式處理HTML.

outerHTML屬性

讀模式:outerHTML返回呼叫它的元素及所有子節點的HTML標籤

寫模式:outerHTML會根據指定的HTML字串建立新的DOM子樹,然後用這個DOM子樹完全替換呼叫元素.

insertAdjacentHTML()方法

該方法接收兩個引數:插入位置和要插入的HTML文字,其中第一個引數是下列值之一:

  • "beforebegin" ,在當前元素之前插入一個緊鄰的同輩元素;
  • "afterbegin" ,在當前元素之下插入一個新的子元素或在第一個子元素之前再插入新的子元素;
  • "beforeend" ,在當前元素之下插入一個新的子元素或在最後一個子元素之後再插入新的子元素;
  • "afterend" ,在當前元素之後插入一個緊鄰的同輩元素。

這些值必須小寫,第二個引數是一個HTML字串,如果瀏覽器無法解析該字串,就會丟擲錯誤.

//作為前一個同輩元素插入
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>");

記憶體與效能問題

在刪除帶有事件處理程式或引用了其他 JavaScript 物件子樹時,就有可能導致記憶體佔用問題。假設某個元素有一個事件處理程式(或者引用了一個 JavaScript 物件作為屬性),在使用前述某個屬性將該元素從文件樹中刪除後,元素與事件處理程式(或 JavaScript 物件)之間的繫結關係在記憶體中並沒有一併刪除。如果這種情況頻繁出現,頁面佔用的記憶體數量就會明顯增加。因此,在使用 innerHTML 、outerHTML 屬性和 insertAdjacentHTML() 方法時,最好先手工刪除要被替換的元素的所有事件處理程式和 JavaScript 物件屬性.

在插入大量新 HTML 標記時,使用 innerHTML 屬性與通過多次 DOM 操作先建立節點再指定它們之間的關係相比,效率要高得多。 這是因為在設定 innerHTML 或 outerHTML 時,就會建立一個 HTML解析器。這個解析器是在瀏覽器級別的程式碼(通常是 C++編寫的)基礎上執行的,因此比執行 JavaScript快得多。不可避免地,建立和銷燬 HTML 解析器也會帶來效能損失,所以最好能夠將設定 innerHTML或 outerHTML 的次數控制在合理的範圍內。

scrollIntoView()方法

HTML5通過該方法作為標準方法,可以在所有HTML元素上呼叫,通過滾動瀏覽器視窗或某個容器元素,呼叫元素就可以出現在視口中.如果給該方法傳入true作為引數或不傳任何引數,那麼視窗滾動之後會讓呼叫元素的頂部與視口頂部儘可能平齊.如果傳入false為引數,呼叫元素會盡可能全部出現在視口中,不過頂部不一定平齊.

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

專有擴充套件

首先我們需要知道什麼是專有擴充套件,很多開發商會向DOM中新增專有擴充套件,用來彌補功能的不足,專有擴充套件為Web開發領域提供了很多重要的功能,這些功能最終都在HTML5規範中得到了標準化.即使如此,還有大量的專有擴充套件沒有被HTML5加入標準中,所以在使用的時候它們還是專有擴充套件.

文件模式

頁面的文件模式決定了可以使用什麼功能。換句話說,文件模式決定了你可以使用哪個級別的 CSS,可以在 JavaScript 中使用哪些 API,以及如何對待文件型別(doctype)。到了 IE9,總共有以下 4 種文件模式。

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

要強制瀏覽器以某種模式渲染頁面,可以使用 HTTP 頭部資訊 X-UA-Compatible ,或通過等價的<meta> 標籤來設定: 

<meta http-equiv="X-UA-Compatible" content="IE=IEVersion">

注意,這裡 IE 的版本( IEVersion )有以下一些不同的值,而且這些值並不一定與上述 4 種文件模式對應。

  • Edge :始終以最新的文件模式來渲染頁面。忽略文件型別宣告。對於 IE8,始終保持以 IE8 標準模式渲染頁面。對於 IE9,則以 IE9 標準模式渲染頁面。
  • EmulateIE9 :如果有文件型別宣告,則以 IE9 標準模式渲染頁面,否則將文件模式設定為 IE5。
  • EmulateIE8 :如果有文件型別宣告,則以 IE8 標準模式渲染頁面,否則將文件模式設定為 IE5。
  • EmulateIE7 :如果有文件型別宣告,則以 IE7 標準模式渲染頁面,否則將文件模式設定為 IE5。
  • 9 :強制以 IE9 標準模式渲染頁面,忽略文件型別宣告。
  • 8 :強制以 IE8 標準模式渲染頁面,忽略文件型別宣告。
  • 7 :強制以 IE7 標準模式渲染頁面,忽略文件型別宣告。
  • 5 :強制將文件模式設定為 IE5,忽略文件型別宣告。

沒有規定說必須在頁面中設定 X-UA-Compatible 。預設情況下,瀏覽器會通過文件型別宣告來確定是使用最佳的可用文件模式,還是使用混雜模式。 

document.documentMode屬性

通過document.documentMode屬性可以知道給定頁面使用的是什麼文件模式,該屬性實在IE8之後新增,返回使用的文件模式的版本號.

children屬性

該屬性是HTMLCollection的例項,只包含元素中同樣還是元素的子節點,除此外children屬性和childNodes沒有什麼區別,即在元素中只包含元素子節點時,這兩個屬性的值相同.

contains()方法

通過該方法可以判斷某個節點是不是另一個節點的後代,注意呼叫contains()方法的應該是祖先節點,也就是搜尋開始的節點.

該方法接收一個引數,即要檢測的後代節點,如果被檢測的節點是後代節點,該方法返回true,否則,返回false.

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

使用 DOM Level 3 compareDocumentPosition() 也能夠確定節點間的關係。這個方法用於確定兩個節點間的關係,返回一個表示該關係的位掩碼( bitmask)。下表列出了這個位掩碼的值。

掩碼 節點關係
無關(給定的節點不在當前文件中)
居前(給定的節點在DOM樹中位於參考節點之前)
居後(給定的節點在DOM樹中位於參考節點之後)
包含(給定的節點是參考節點的祖先)
16 被包含(給定的節點是參考節點的後代)

為模仿contains()方法,我們應該關注的是掩碼16,對compareDocumentPosition()的結果執行按位與,以確定參考節點.如下例所示:

var result = document.documentElement.compareDocumentPosition(document.body);
alert(!!(result & 16));

通過對瀏覽器及能力檢測,我們可以寫一個通用的contains函式:

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屬性

通過 innertText 屬性可以操作元素中包含的所有文字內容,包括子文件樹中的文字。在通過innerText 讀取值時,它會按照由淺入深的順序,將子文件樹中的所有文字拼接起來。在通過innerText 寫入值時,結果會刪除元素的所有子節點,插入包含相應文字值的文字節點。

由於不同瀏覽器處理空白符的方式不同,因此輸出的文字可能會也可能不會包含原始 HTML 程式碼中的縮排.

注意,設定 innerText 屬性移除了先前存在的所有子節點,完全改變了 DOM 子樹。此外,設定 innerText屬性的同時,也對文字中存在的 HTML 語法字元(小於號、大於號、引號及和號)進行了編碼。如下例:

div.innerText = "Hello & welcome, <b>\"reader\"!</b>";

執行上面的一行程式碼後,會得到下面的結果:

<div id="content">Hello &amp; welcome, &lt;b&gt;&quot;reader&quot;!&lt;/b&gt;</div>

設定 innerText 永遠只會生成當前節點的一個子文字節點,而為了確保只生成一個子文字節點,就必須要對文字進行 HTML 編碼。利用這一點,可以通過 innerText 屬性過濾掉 HTML 標籤。方法是將 innerText 設定為等於 innerText ,這樣就可以去掉所有 HTML 標籤,如:

div.innerText = div.innerText

執行這行程式碼後,就用原來的文字內容替換了容器元素中的所有內容(包括子節點,因而也就去掉了 HTML 標籤)。

支援與innerText作用類似的 textContent 屬性。 textContent 是 DOM Level 3 規定的一個屬性.

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

outerText屬性

讀模式:除了作用範圍擴大到了包含呼叫它的節點之外, outerText 與 innerText 基本上沒有多大區別。在讀取文字值時, outerText 與 innerText 的結果完全一樣。

寫模式:outerText 不只是替換呼叫它的元素的子節點,而是會替換整個元素(包括子節點)。

滾動

HTML5將scrollIntoView()納入規範後,仍有幾個其他專有方法可以在不同的瀏覽器中使用,下面幾個方法都是對HTMLElement型別的擴充套件,因此在所有的元素中都可呼叫:

  • scrollIntoViewIfNeeded(alignCenter) :只在當前元素在視口中不可見的情況下,才滾動瀏覽器視窗或容器元素,最終讓它可見。如果當前元素在視口中可見,這個方法什麼也不做。如果將可選的 alignCenter 引數設定為 true ,則表示儘量將元素顯示在視口中部(垂直方向).Safari 和 Chrome 實現了這個方法。
  • scrollByLines(lineCount) :將元素的內容滾動指定的行高, lineCount 值可以是正值,也可以是負值。Safari 和 Chrome 實現了這個方法。
  • scrollByPages(pageCount) :將元素的內容滾動指定的頁面高度,具體高度由元素的高度決定。Safari 和 Chrome 實現了這個方法。