跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByClassName
按照类名获取元素 -- getElementsByClassName
(HTML5)
标准
- WHATWG 在
Document
与Element
上均有定义,原型HTMLCollection getElementsByClassName(DOMString classNames)
,并定义了匹配算法和类名的提取算法,注意这里是先从参数里提取出类名作为一个set,然后再开始匹配的。其中指明了在quirks mode下类名大小写不敏感,否则大小写敏感 - DOM 4(
Document
,Element
)基本和WHATWG一致
注意点
按照标准里的说法,“Unless otherwise stated, a collection must be live. ”。既然标准没有指明getElementsByClassName
的返回值说是static的,那么它返回的HTMLCollection
就是live的。
WHATWG描述getElementsByClassName
的实现是先从参数里用ASCII空白字符分割出类名,将得到的类名组成一个有序集合之后,将能够匹配集合内所有类名的元素返回。除非在quirks mode否则大小写敏感,Document
及Element
上均有定义。
兼容性
- 属于HTML5新出现的API,IE9+ 及其他浏览器的现行版本均支持
WebKit代码分析
和之前的几个方法一样,在ContainerNode
里实现,使用ClassNodeList
作为NodeListsNodeData::addCacheWithAtomicName<>
的template specialization。之前的一次commit将nodeMatches
改为了elementMatches
,不过github上的mirror里似乎ClassNodeList
还没改掉。ClassNodeList
的nodeMatches
过滤标准(参考WebCore/dom/ClassNodeList.h)为:
if (!element->hasClass()) return false; if (!m_classNames.size()) return false; // FIXME: DOM4 allows getElementsByClassName to return non StyledElement. // https://bugs.webkit.org/show_bug.cgi?id=94718 if (!element->isStyledElement()) return false; return element->classNames().containsAll(m_classNames);
可以看到里面目前有个多余的StyledElement
判定……这里首先判断要过滤的元素有没有class或者传入的字符串是否不含有效的class,接着检查元素是否为StyledElement
(找不到相关文档,不过Node.h里的enum ConstructionType可以看出哪些元素是StyledElement
)。
类名的提取实现在SpaceSplitString
。ClassNodeList
和ElementData
均有SpaceSplitString
的m_classNames
来维护类名集合。这些外部的类通过调用SpaceSplitString::set
来更新和设置类名,而SpaceSplitString::set
又调用SpaceSplitStringData::create
来维护存有集合的私有成员SpaceSplitStringData m_data
。
SpaceSplitStringData::create
有两个重载,外部调用的是SpaceSplitStringData::create (const AtomicString& keyString)
。里面首先调用维护缓存的spaceSplitStringTable
(实质是一个static的hashmap)来检查是否已经有缓存,如果没有则开始创建集合。 WebCore/dom/SpaceSplitString.cpp里的tokenizeSpaceSplitString
实现了将类名字符串tokenize的算法,并且每分解出一个token,就会调用参数tokenProcessor
的processToken
。SpaceSplitStringData::create (const AtomicString& keyString)
首先以TokenCounter
作为tokenProcessor
调用tokenizeSpaceSplitString
,将类名字符串先扫描一次统计token数目以方便分配内存,接着调用重载的SpaceSplitStringData::create(const AtomicString& keyString, unsigned tokenCount)
,里面使用TokenAtomicStringInitializer
作为tokenProcessor
再扫描一次字符串,将每个token的内容保存到分配好的内存里。
判断匹配的算法就比较粗暴了……参见WebCore/dom/SpaceSplitString.cpp的containsAll和WebCore/dom/SpaceSplitString.h的contains会发现,居然是线性查找套线性查找的O(mn)(m和n分别为要查找的类名数和被查找的元素的实际类名数)……不过考虑到应用场景下一般类名很少,多点也就五六个了,被查找的类名可能更少,乘起来mn可能最多也就二三十左右,一般大概不会超过十,此时用高级数据结构带来的overhead可能还大过简单的线性查找,所以这样实现也是合理的。