按照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 基本一致
注意點
name
和id
不同,可以重復,因此這個方法名字里有“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版本返回的不是NodeList
,HTMLCollection
,不過因為HTMLCollection
兼容NodeList
,所以沒有大礙。
其他
在 DOM HTML 里,name
只出現在一部分元素的IDL里(有哪些元素的IDL里帶有attribute DOMString name 可參見 DOM Level 2 和 WHATWG,或者參考HTML4 DTD),而其他元素的name
實際上是作為一個普通的Attr
Node 而不是元素IDL里本身帶的屬性,通過NamedNodeMap
實現的(參見DOM 3 和 WHATWG)。因此對於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),實質上使用了ContainerNode
的getElementsByName
。
ContainerNode
的getElementsByName
使用NameNodeList
作為NodeListsNodeData::addCacheWithAtomicName<>
的template specialization (見WebCore/dom/ContainerNode.cpp)。注意NodeListsNodeData::addCacheWithAtomicName<>
的模板提高了代碼的重用——只需要為template specialization的類定義create
、elementMatches
等函數,即可使用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
繼承 CachedLiveNodeList
,CachedLiveNodeList
的迭代器使用的collectionBegin()
,collectionTraverseForward()
等(見WebCore/dom/LiveNodeList.h)會遍歷需要過濾的root node的后代,使用虛函數elementMatches
過濾(參見WebCore/dom/LiveNodeList.h)。NamedNodeList
實現的elementMatches
以element.getNameAttribute() == m_name
作為過濾標准(見WebCore/dom/NameNodeList.h)。
值得注意的是CachedLiveNodeList
的elementMatches
從LiveNodeList
繼承而來,而在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。因此NamedNodeList
的element.getNameAttribute()
不管name
在IDL里還是作為本身的屬性都會一並將其返回,反映到上層就是getElementsByName()
也不需要管name
是否在IDL里。