jsbook
jsbook copied to clipboard
【新福利】选择器引擎 JavaScript 底层是如何查找指定元素的?
http://www.zhihu.com/question/29261736
getElementById() 不是JavaScript语言的功能,而是在浏览器作为JavaScript的host环境注册给JavaScript的host函数,由浏览器内部实现。
假如我们讨论的是顶层document对象的getElementById()。
以Chrome/Chromium浏览器为例,其核心Blink实现了DOM,在core/dom。 里面有:
Element* TreeScope::getElementById(const AtomicString& elementId) const
{
if (elementId.isEmpty())
return 0;
if (!m_elementsById)
return 0;
return m_elementsById->getElementById(elementId, this);
}
其中TreeScope::m_elementsById是一个DocumentOrderedMap。后者的实际存储用了一个HashMap<StringImpl*, OwnPtr<MapEntry> >,就是个简单的哈希表而已,记录着ID到Element的映射关系。每当一个新的有ID的Element被加到DOM树上时,相应的映射关系就会被加到这个m_elementsById哈希表里。 Chromium的Document类是TreeScope的子类,继承了这个getElementById()的实现。
core/dom/Document.idl告诉Chromium与V8的binding如何把Document类注册给V8:
[PerWorldBindings] Element getElementById(DOMString elementId);
(最新的Chromium里这个声明在core/dom/NonElementParentNode.idl) 关于IDL请参考文档:Web IDL in Blink
Blink的IDL文件由W3C标准的IDL衍生而来,这样可以方便的确保自己实现的接口符合标准。W3C DOM Level 3标准请参考:IDL Definitions
getElementById()经过包装、注册到JavaScript引擎实例的global对象里,实现就完成了。
Blink由WebKit衍生而来,在DOM的核心实现目前大体上还是一样的。
下面评论有同学问querySelector的实现,其实跟上面的分析过程一样自己顺着找就好了。 Blink的Document的querySelector实现在其另外一个基类ContainerNode,core/dom/ContainerNode.cpp:
PassRefPtr<Element> ContainerNode::querySelector(const AtomicString& selectors, ExceptionState& exceptionState)
{
if (selectors.isEmpty()) {
exceptionState.throwDOMException(SyntaxError, "The provided selector is empty.");
return nullptr;
}
SelectorQuery* selectorQuery = document().selectorQueryCache().add(selectors, document(), exceptionState);
if (!selectorQuery)
return nullptr;
return selectorQuery->queryFirst(*this);
}
底下的具体实现可以参考 SelectorDataList::execute(),可见有对特例的快速路径和对别的情况的慢速路径处理。例如说对#id形式的查询可能就getAllElementsById()一把然后从中找出selector匹配的就完事了。
然后一样通过IDL将自己注册给JavaScript引擎。 [RaisesException] Element querySelector(DOMString selectors); 搞定。