你不知道的那些DOM

ZhangCheng發表於2018-05-01

DOM變化

DOM2級和3級的目的在於擴充套件DOM API,以滿足操作XML的所有需求,同時提供更好的錯誤處理及特性檢測能力。從某種意義上講,實現這一目的的很大程度意味著對名稱空間的支援。

var supportsDOM2Core = document.implementation.hasFeature('Core', '2.0');

var supportsDOM3Core = document.implementation.hasFeature('Core', '3.0');

var supportsDOM2HTML = document.implementation.hasFeature('HTML', '2.0')

var supportsDOM2Views = document.implementation.hasFeature('Views', '2.0');

var supportsDOM2XML = document.implementation.hasFeature('XML', '2.0')
複製程式碼

針對XML名稱空間的變化

有了XML名稱空間,不同XML文件的元素就可以混合一起,共同構成格式良好的文件,而不必擔心發生命名衝突。

名稱空間要使用xmlns特性來指定。

<html xmlns='http://www.w3.org/1999/xhtml'>
    <head>
        <title>Example XHTML page</title>
    </head>
    <body>
        Hello world!
    </body>
</html>
複製程式碼

Node型別的變化

在DOM2級中,Node型別包含下列特定於名稱空間的屬性。

  • localName: 不帶名稱空間字首的節點名稱
  • namespaceURI: 名稱空間URI或者(在未指定的情況下是)null
  • prefix: 名稱空間字首或者(未指定的情況下是)null

其他方面的變化

DOM的其他部分在‘DOM2級核心’中也發生了一些變化。這些變化與XML名稱空間無關,而是更傾向於確保API的可靠性及完整性。

DocumentType型別的變化

新增了3個屬性:publicId、systemId和internalSubset。

Document型別的變化

Document型別的變化中唯一與名稱空間無關的方法是importNode()。

需要注意的是,每個節點都有一個ownerDocument屬性,表示所屬的文件。如果呼叫appendChild()時傳入的節點屬於不同的文件(ownerDocument屬性的值不一樣),則會導致錯誤。但在呼叫importNode()時傳入不同文件的節點則會返回一個新節點,這個新節點的所有權歸當前文件所有。

importNode()方法與Element的cloneNode()方法非常類似,它接受兩個引數:要複製的節點和一個表示是否複製子節點的布林值。
返回的結果是原來節點的副本,但能夠在當前檔案中使用。

var newNode = document.importNode(oldNode, true);  //匯入節點及其子節點

document.body.appendChild(newNode);
複製程式碼

Node型別的變化

Node型別中唯一與名稱空間無關的變化,就是新增了isSupported()方法。與DOM1級為document.implementation引入的hasFeature()方法類似,isSupported()方法用於確定當前節點具有什麼能力。

DOM3級引入了兩個輔助比較節點的方法:isSameNode()和isEqualNode()。 這兩個方法都接受一個節點引數,並在傳入節點與引用的節點相同或相等時返回true。
所謂相同,指的是兩個節點引用的是同一個物件。
所謂相等,指的是兩個節點是相同的型別,具有相等的屬性(nodeName、nodeValue),而且它們的attributes和childNodes屬性也相等。

var div1 = document.createElement('div');

div1.setAttribute('class', 'box');

var div2 = docuemnt.createElement('div');

div2.setAttribute('class', 'box');

div1.isSameNode(div1);  // true
div1.isSameNode(div2); // false
div1.isEqualNode(div2); // true
複製程式碼

框架的變化

樣式

在HTML中定義樣式的方式有3種:通過<link/>元素包含外部樣式表檔案、使用<style/>元素定義嵌入式樣式,以及使用style特性定義針對特定元素的樣式。

DOM2級樣式模組圍繞這3種應用樣式的機制提供了一套API。

訪問元素的樣式

任何支援style特性的HTML元素在JavaScript中都有一個對應的style屬性。這個style物件是CSSStyleDeclaration的例項,包含著通過HTML的style特性指定的所有樣式資訊,但不包含與外部樣式表或嵌入樣式表經層疊而來的樣式。

對於短劃線的CSS屬性名,必須將其轉換成駝峰大小寫形式,才能通過JavaScript訪問。

  • background-image -> style.backgroundImage
  • font-family -> style.fontFamily

其中一個不能直接轉換的CSS屬性就是float。由於float是JavaScript中的保留字,因此不能用作屬性名。規定對應的屬性名是cssFloat。而IE中則是styleFloat。

DOM樣式屬性和方法

DOM2級樣式規範還為style物件定義了一些屬性和方法。這些屬性和方法在提供元素的style特性值的同時,也可以修改樣式。下面列出了這些屬性和方法。

  • cssText
  • length
  • parentRule
  • getPropertyCSSValue(propertyName)
  • getPropertyPriority(propertyName)
  • getPropertyValue(propertyName): 返回給定屬性的字串值
  • item(index): 返回給定位置的CSS屬性的名稱
  • removeProperty(propertyName):從樣式中刪除給定屬性。
  • setProperty(propertyName, value, priority)

設定cssText是為元素應用多項變化最快捷的方式:

myDiv.style.cssText = 'width: 25px; height: 100px'
複製程式碼

迭代CSS屬性:

var prop, value, i, len;

for (i = 0; len = myDiv.style.length; i < len; i++) {
    prop = myDiv.style[i];  //或者 myDiv.style.item(i)
    
    value = myDiv.style.getPropertyValue(prop);
    
    console.log(prop + ' : ' + value)
}
複製程式碼

計算的樣式

getComputedStyle()方法。這個方法接受兩個引數:要取得計算樣式的元素和一個偽元素字串。如果不需要偽元素資訊,第二個引數可以是null。

var myDiv = document.getElementById('myDiv');

var computedStyle = myDiv.computedStyle // IE環境下 myDiv.currentStyle
複製程式碼

這個屬性是CSSStyleDeclaration的例項。

操作樣式表

CSSStyleSheet物件是一套只讀的介面(有一個屬性例外)。使用下面的程式碼可以確定瀏覽器是否支援DOM2級樣式表。

var supportDOM2StyleSheets = dcoument.implementation.hasFeature('StyleSheets', '2.0');
複製程式碼

CSSStyleSheet繼承自StyleSheet,後者可以作為一個基礎介面來定義非CSS樣式表。從StyleSheet介面繼承而來的屬性如下。

  • disabled: 表示樣式表是否被禁用的布林值。
  • href:如果樣式表是通過包含的,則是樣式表的URL,否則是null
  • media:當前樣式表支援的所有媒體型別的集合
  • ownerNode: 指向擁有當前樣式的節點的指標,樣式表可能是在HTML中通過<link><style/>引入的。如果當前樣式表是其他樣式表通過@import匯入的,則這個屬性值為null。IE不支援這個屬性。
  • parentStyleSheet: 在當前樣式表通過@import匯入的情況下,這個屬性是一個指向匯入它的樣式表的指標。
  • title: ownerNode中title屬性的值。
  • type:表示樣式表型別的字串。對CSS樣式表而言,這個字串是'text/css'。
  • cssRules:樣式表中包含的樣式規則的集合。IE中類似的是rules屬性。
  • ownerRule:如果樣式表是通過@import匯入的,這個屬性就是一個指標,指向表示匯入的規則;否則值為null。
  • deleteRule(index):刪除cssRules集合中指定位置的規則。
  • insertRule(rule, index):向cssRules集合中指定的位置插入rule字串。

應用於文件的所有樣式表是通過document.styleSheets集合來表示的。

var sheet = null;

for (var i = 0, len = document.styleSheets.length; i < len; i++) {
    sheet = document.styleSheets[i];
    console.log(sheet.href)
}

複製程式碼

元素大小

偏移量

offset dimension,包括元素在螢幕上佔用的所有可見的空間。通過下面四個屬性可以取得元素的偏移量。

  • offsetHeight
  • offsetWidth
  • offsetLeft
  • offsetTop

其中offsetLeft和offsetTop屬性與包含元素有關,包含元素的引用儲存在offsetParent屬性中。offsetParent屬性不一定與parentNode的值相等。例如,元素的offsetParent是作為其祖先元素的

元素,因為
是在DOM層次中距
最近的一個具有大小的元素。

function getElementOffsetLeft (element) {
   var currentLeft = element.offsetLeft;
   
   var parent = element.offsetParent;
   
   while (parent) {
       currentLeft += parent.offsetLeft;
       parent = parent.offsetParent;
   }
   
   return currentLeft;
}

同理 // top
複製程式碼

客戶區大小 client dimension

是指元素內容及其內邊距所佔據的空間大小(content + padding)。

clientWidth屬性是元素內容區寬度加上左右內邊距寬度;clientHeight屬性是元素內容區高度加上上下內邊距高度。

常見用法:確定瀏覽器視口大小。

function getViewport () {
    if (document.compatMode == 'BackCompat') {
        return {
            width: document.body.clientWidth,
            height: document.body.clientHeight
        }
    } else {
        return {
            width: document.documentElement.clentWidth,
            height: document.documentElement.clientHeight
        }
    }
}
複製程式碼

滾動大小(scroll dimension)

指的是包含滾動內容的元素的大小。

4個與滾動大小相關的屬性:

  • scrollHeight: 在沒有滾動條的情況下,元素內容的總高度。
  • scrollWidth: 在沒有滾動條的情況下,元素內容的總寬度。
  • scrollLeft:被隱藏在內容區左側的畫素數。
  • scrollTop: 被隱藏在內容區域上方的畫素數。

因為瀏覽器相容性的問題,在確定文件的總高度時,必須取得scrollWidth/clientWidth和scrollHeight/clientHeight中的最大值,才能保證在跨瀏覽器時得到精確的結果。

var docHeight = Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight);

var docWidth = Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth);
複製程式碼

遍歷

DOM2級遍歷和範圍模組定義了兩個用於輔助完成順序遍歷DOM結構的型別:NodeIterator和TreeWalker

var supportTraversals = document.implementation.hasFeature('Traversal', '2.0');

var supportNodeIterators = (typeof document.createNodeIterator == 'function');

var supportTreeWalker = (typeof document.createTreeWaler == 'function')
複製程式碼

NodeIterator

NodeIterator型別是兩者中比較簡單的一個,可以使用document.createNodeIterator()方法建立它的新例項。這個方法接受下列4個引數。

  • root: 想要作為搜尋起點的樹中的節點。
  • whatToShow:表示要訪問哪些節點的數字程式碼
  • filter:是一個NodeFilter物件,或者一個表示應該接受還是拒絕某種特定的的函式。
  • entityReferenceExpansion:布林值,表示是否要擴充套件實體引用。這個引數在HTML頁面中沒有用,因為其中的實體引用不能擴充套件。

whatToShow引數是一個位掩碼,通過應用一或多個filter來確定要訪問哪些節點。這個引數的值以常量形式在NodeFilter型別中定義。

  • NodeFilter.SHOW_ALL:顯示所有型別的節點。
  • NodeFilter.SHOW_ELEMENT:顯示元素節點。
  • NodeFilter.SHOW_ATTRIBUTE:顯示特性節點。由於 DOM 結構原因,實際上不能使用這個值。
  • NodeFilter.SHOW_TEXT:顯示文字節點。
  • NodeFilter.SHOW_CDATA_SECTION:顯示 CDATA 節點。對 HTML 頁面沒有用。
  • NodeFilter.SHOW_ENTITY_REFERENCE:顯示實體引用節點。對 HTML 頁面沒有用。
  • NodeFilter.SHOW_ENTITYE:顯示實體節點。對 HTML 頁面沒有用。
  • NodeFilter.SHOW_PROCESSING_INSTRUCTION:顯示處理指令節點。對 HTML 頁面沒有用。
  • NodeFilter.SHOW_COMMENT:顯示註釋節點。
  • NodeFilter.SHOW_DOCUMENT:顯示文件節點。
  • NodeFilter.SHOW_DOCUMENT_TYPE:顯示文件型別節點。
  • NodeFilter.SHOW_DOCUMENT_FRAGMENT:顯示文件片段節點。對 HTML 頁面沒有用。
  • NodeFilter.SHOW_NOTATION:顯示符號節點。對 HTML 頁面沒有用。

除了NodeFilter.SHOW_ALL之外,可以使用按位或操作符來組合多個選項:

var whatToShow = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT;
複製程式碼

可以通過createNodeIterator()方法的filter引數來指定自定義的NodeFilter物件,或者指定一個功能類似節點過濾器(node filter)的函式。

每個NodeFilter物件只有一個方法,即accept-Node();如果應該訪問給定的節點,該方法返回NodeFilter.FILTER_ACCEPT,如果不應該訪問給定的節點,該方法返回NodeFilter.FILTER_SKIP。由於NodeFilter是一個抽象的型別,因此不能直接建立它的例項。在必要時,只要建立一個包含acceptNode()方法的物件,然後將這個物件傳入createNodeIterator()中即可。

var filter = {
    acceptNode: function (node) {
        return node.tagName.toLowerCase() == 'p' ?
        NodeFilter.FILTER_ACCEPT :
        NodeFilter.FILTER_SKIP;
    }
};

var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false);
複製程式碼

第三個引數也可以是一個與acceptNode()方法類似的函式,如下所示。

var filter = function (node) {
   return node.tagName.toLowerCase() == 'p' ?
   NodeFilter.FILTER_ACCEPT :
   NodeFilter.FILTER_SKIP;
};

var iterator = document.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, filter, false);
複製程式碼

如果不指定過濾器,那麼應該在第三個引數的位置上傳入null。

NodeIterator型別的兩個主要方法是nextNode()和previousNode()。在剛剛建立的NodeIterator物件中,有一個內部指標指向根節點,因此第一次呼叫nextNode()會返回根節點。

TreeWalker

TreeWalker是NodeIterator的一個更高階的版本。除了包括nextNode()和previousNode()在內的相同的功能之外,這個型別還提供了下列用於在不同方向上遍歷DOM結構的方法。

  • parentNode(): 遍歷到當前節點的父節點。
  • firstChild(): 遍歷到當前節點的第一個子節點。
  • lastChild(): 遍歷到當前節點的最後一個子節點
  • nextSibling(): 遍歷到當前節點的下一個同輩節點。
  • previousSibling(): 遍歷到當前節點的上一個同輩節點。

範圍

為了讓開發人員更方便地控制頁面,“DOM2級遍歷和範圍”模組定義了“範圍”(range)介面。通過範圍可以選擇文件中的一個區域,而不必考慮節點的界限。

DOM中的範圍

DOM2級在Document型別中定義了createRange()方法。在相容DOM的瀏覽器中,這個方法屬於document物件。

var supportsRange = document.implementation.hasFeature('Range', '2.0');

var supportsRange2 = (typeof document.createRange == 'function');
複製程式碼

如果瀏覽器支援範圍,那麼就可以使用createRange()來建立DOM範圍。

var range = document.createRange();
複製程式碼

與節點類似,新建立的範圍也直接與建立它的文件關聯在一起,不能用於其他文件。

IE8及更早版本中的範圍

小結

DOM2級規範定義了一些模組,用於增強DOM1級

  • DOM2級核心為不同的DOM型別引入了一些與XML名稱空間有關的方法。還定義了以程式設計方式建立Document例項的方法,也支援建立DocumentType物件。
  • DOM2級樣式模組主要針對操作元素的樣式資訊而開發,其特性簡要總結如下:
    • 每個元素都有一個關聯的style物件,樂意用來確定和修改行內的樣式。
    • 要確定某個元素的計算樣式,可以使用getComputedStyle()方法。
    • IE不支援getCumputedStyle()方法,但提供了currentStyle()方法。
    • 可以通過document.styleSheets集合訪問樣式表。
    • 除IE之外的所有瀏覽器都支援針對樣式表的這個介面,IE也為幾乎所有相應的DOM功能提供了自己的一套屬性和方法。
  • DOM2級遍歷和範圍模組提供了與DOM結構互動的不同方式:
    • 遍歷即使用NodeIterator或TreeWalker對DOM執行深度優先的遍歷。
    • NodeIterator是一個簡單的介面,只允許以一個節點的步幅前後移動。而treeWalker在提供相同功能的同時,還支援在DOM結構的各個方向上移動,包括父節點、同輩節點和子節點等方向。
    • 範圍是選擇DOM結構中特定部分,然後再執行相應操作的一種手段。
    • 使用範圍選區可以在刪除文件中某些部分的同時,保持文件結構的格式良好,或者複製文件中的相應部分。
    • IE8及更早版本不支援DOM2級遍歷和範圍模組,但它提供了一個專有的文字範圍物件,可以用來完成簡單的基於文字的範圍操作。IE9完全支援DOM遍歷。

相關文章