跟隨標准與Webkit源碼探究DOM -- 獲取元素之getElementsByName


按照name屬性獲取多元素 -- getElementsByName

標准

  • DOM 1 定義在HTMLDocument Interface 中,原型NodeList getElementsByName(in DOMString elementName),該方法不會拋出任何異常。
  • DOM 2依然定義在HTMLDocument,原型不變,但是新增說明在 HTML4.0 里搜索范圍為所有元素,而 XHTML 1.0 里搜索范圍縮小到表單元素
  • DOM 3沒有 DOM HTML 的標准,沿襲 DOM 2(DOM 3 有 Document 所屬的 DOM core標准,但HTMLDocument屬於 DOM HTML 而不屬於 DOM core)
  • WHATWG 在 DOM HTML 標准里 override 了 Document 而不是另開一個 `HTMLDocument,原型不變
  • W3C HTML5和 WHATWG 基本一致

注意點

  • nameid 不同,可以重復,因此這個方法名字里有“s”,並且返回的是NodeList
  • 這個方法返回的是一個 “live” 的NodeList,當頁面元素改變后,再次調用獲得的NodeList會跟着更新。
  • 當沒有符合要求的元素時,返回的不是 null,是一個空的NodeList
  • 元素的 name有兩種,一種已經在該元素的IDL里,另一種只是名字為name的屬性(Attr
  • 一些瀏覽器還提供document.name這種直接獲取name為name的元素的方式,但這個特性並未出現在標准中,一些新的瀏覽器也開始不支持這種獲取方式了,所以最好不要用

兼容性

IE9- 只算入 HTML4 允許帶 name 的元素(換句話說,只算入IDL里有name的元素)。但是它們又有一個 bug :算入任何 id 與所搜索的name相同的元素。

檢查

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="foo"></div>
    <a name="bar"></a>
    <div name="baz"></div>
</body>
</html>

IE 9- 下:

  • document.getElementsByName('baz').length返回 0(因為 HTML 4 中 div 不能帶name)
  • document.getElementsByName('foo').length 返回 1(算入了 HTML 4 中不能帶 name 但 id 與查詢的name相同的div)
  • document.getElementsByName('bar').length 返回 1,是正常行為。

FireFox 與 Chrome 返回 1,0,1,即允許任意元素帶 name,且不會將 id 與 name混淆。

因此在IE 9-下,使用該方法獲取的元素可能還需要用 elem.name == name 進行過濾。此外,某些IE版本返回的不是NodeListHTMLCollection,不過因為HTMLCollection兼容NodeList,所以沒有大礙。

其他

在 DOM HTML 里,name 只出現在一部分元素的IDL里(有哪些元素的IDL里帶有attribute DOMString name 可參見 DOM Level 2WHATWG,或者參考HTML4 DTD),而其他元素的name實際上是作為一個普通的Attr Node 而不是元素IDL里本身帶的屬性,通過NamedNodeMap實現的(參見DOM 3WHATWG)。因此對於IDL里沒有name的元素,不能直接用elem.name 獲取name,但用getAttribute則都可以獲取。例如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div name="baz"></div>
    <a name="bar"></a>
</body>
</html>

在 console 里:

var div = document.getElementsByName('baz')[0];
div.name; // undefined
div.getAttribute('name'); // baz

var a = document.getElementsByName('bar')[0];
a.name; // bar
a.getAttribute('name'); // bar

Webkit 代碼分析

Document 繼承 ContainerNode (見WebCore/dom/Document.h),實質上使用了ContainerNodegetElementsByName

ContainerNodegetElementsByName使用NameNodeList作為NodeListsNodeData::addCacheWithAtomicName<>的template specialization (見WebCore/dom/ContainerNode.cpp)。注意NodeListsNodeData::addCacheWithAtomicName<>的模板提高了代碼的重用——只需要為template specialization的類定義createelementMatches等函數,即可使用addCacheWithAtomicName實現live的NodeList的過濾+緩存。WebKit將這些函數都匯總在了CachedLiveNodeList這個類里,只要繼承這個類,實現它和它繼承的虛函數,就可以用於addCacheWithAtomicName的template specialization(參見WebCore/dom/LiveNodeList.h),建立一個帶有緩存和特定過濾標准的NodeList。

NodeListsNodeData使用一個私有的NodeListAtomicNameCacheMap成員m_atomicNameCaches實現緩存。當addCacheWithAtomicName被調用時,首先檢查是否已存在對應的緩存,若存在,用m_atomicNameCaches.fastAdd快速更新(參見WebCore/dom/NodeRareData.h(注意NodeListAtomicNameCacheMap本質上是一個hash map,參見typedef定義)。如果沒有緩存,調用模版類的create函數創建新的模版類對象並返回,這里為NameNodeList

NamedNodeList 繼承 CachedLiveNodeListCachedLiveNodeList的迭代器使用的collectionBegin()collectionTraverseForward()等(見WebCore/dom/LiveNodeList.h)會遍歷需要過濾的root node的后代,使用虛函數elementMatches過濾(參見WebCore/dom/LiveNodeList.h)。NamedNodeList實現的elementMatcheselement.getNameAttribute() == m_name作為過濾標准(見WebCore/dom/NameNodeList.h)。

值得注意的是CachedLiveNodeListelementMatchesLiveNodeList繼承而來,而在LiveNodeList的原型里elementMatches的原型為virtual bool elementMatches(Element&) const = 0——僅僅是個虛函數,不需要inline(參見WebCore/dom/LiveNodeList.h),但是在NamedNodeList的實現里這個函數被 inline 了。眾所周知虛函數的調用要查表會帶來較高的開銷,對於這樣會被高頻率調用的函數來說顯然是不行的,這里其實是通過 inline 來繞過這個開銷。注意 virtual 與 inline 不沖突的條件是編譯器需要在編譯時知道將這個虛函數做inline實現的類是什么(而不能像普通的virtual調用一樣留到運行時確定),而CachedLiveNodeList里凡是調用elementMatches的地方都會有類似auto& nodeList = static_cast<const NodeListType&>(*this)的語句先利用模版確定自己的靜態類型,然后再使用這個確定了靜態類型的引用而不是this來調用elementMatches,所以不會沖突(這種寫法名為Curiously Recurring Template Pattern,能夠實現出所謂的static polymorphism來繞開虛函數調用的開銷又達到虛函數調用的目的)。

這樣繞個大彎(用上模版)為虛函數添加 inline 通常是為了性能考慮,參見Stackoverflow上的相關問題,這里剛好符合應用場景——elementMatches注定會被頻繁調用。畢竟getElementsByName經常會直接在document上執行,那就會遍歷文檔里所有的節點,每遍歷一個就要調用一次elementMatches來過濾,那通常至少也是上百甚至上千上萬的調用……同樣地,在ElementDescendantIterator里幾乎所有的方法(包括構造函數)都被inline了,就是因為它作為遍歷單位會被頻繁調用方法,所以需要 inline 來榨干性能(參見WebCore/dom/ElementDescendantIterator.h

其他值得注意的點:

  • webkit是通過對實現定義好的 flag 做位運算來設置和判斷元素是否擁有某個屬性的(比如name),用一個32位的整數來為ElementData保存數組長度和flag(參見WebCore/dom/ElementData.h,,這樣省空間又省時間,並且能夠對IDL定義的屬性和自定義的屬性一視同仁。
  • Node里也是通過事先定義好的 flag 位運算來得知衍生類類型的(而不是使用C++昂貴的RTTI),flag的定義參見WebCore/dom/Node.h
  • 另外對於IDL里含有name的元素,webkit實際上是包了一個element.getNameAttribute來返回name的,比如<a>參見 WebCore/html/HTMLAnchorElement.cpp。因此NamedNodeListelement.getNameAttribute()不管name在IDL里還是作為本身的屬性都會一並將其返回,反映到上層就是getElementsByName()也不需要管name是否在IDL里。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM