跟随标准与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里。
时间: 2024-10-18 01:45:13

跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByName的相关文章

跟随标准与Webkit源码探究DOM -- 获取元素之getElementById

按照ID获取元素 -- getElementById 标准 DOM 1,定义在HTMLDocument Interface 中,原型Element getElementById(in DOMString elementId),当不存在拥有对应ID的元素时返回null,该方法不会抛出任何异常. DOM 2,移动到了Document(原HTMLDocument的Parent Interface),原型不变. DOM 3 特别声明浏览器应当使用Attr.isId判断 Attr 是否为 ID,同时加了一

跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByTagName

按照标签名获取元素 -- getElementsByTagName 标准 DOM 1在Element和Document两个interface中均有定义,原型NodeList getElementsByTagName(in DOMString tagname),指明按照先序遍历遇到的顺序排列,不会抛出任何异常,参数"*"返回对应document或者element下所有元素.注意这里指明返回的是一个live的仅含有Element的NodeList. DOM 2里定义仍在Element和Do

跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByClassName

按照类名获取元素 -- getElementsByClassName(HTML5) 标准 WHATWG 在Document与Element上均有定义,原型 HTMLCollection getElementsByClassName(DOMString classNames),并定义了匹配算法和类名的提取算法,注意这里是先从参数里提取出类名作为一个set,然后再开始匹配的.其中指明了在quirks mode下类名大小写不敏感,否则大小写敏感 DOM 4(Document,Element )基本和W

跟随标准与Webkit源码探究DOM -- 获取元素之querySelector,querySelectorAll

使用CSS选择器获取元素 -- querySelector,querySelectorAll(HTML5) 标准 W3C Selector API Level 1为Document,DocumentFragment和Element追加了querySelector和querySelctorAll,原型为Element? querySelector(DOMString selectors)和 NodeList querySelectorAll(DOMString selectors),说明了匹配的算

Vue源码探究-虚拟DOM的渲染

Vue源码探究-虚拟DOM的渲染 在虚拟节点的实现一篇中,除了知道了 VNode 类的实现之外,还简要地整理了一下DOM渲染的路径.在这一篇中,主要来分析一下两条路径的具体实现代码. 按照创建 Vue 实例后的一般执行流程,首先来看看实例初始化时对渲染模块的初始处理.这也是开始 mount 路径的前一步.初始包括两部分,一是向 Vue 类原型对象上挂载渲染相关的方法,而是初始化渲染相关的属性. 渲染的初始化 下面代码位于vue/src/core/instance/render.js 相关属性初始

Vue源码探究-事件系统

Vue源码探究-事件系统 本篇代码位于vue/src/core/instance/events.js 紧跟着生命周期之后的就是继续初始化事件相关的属性和方法.整个事件系统的代码相对其他模块来说非常简短,分几个部分来详细看看它的具体实现. 头部引用 import { tip, toArray, hyphenate, handleError, formatComponentName } from '../util/index' import { updateListeners } from '../

windows7下cygwin+vs2013编译webkit源码

先下载源码和其他依赖,然后准备cygwin的环境,安装vs2013,最后编译即可.网上没有能直接用于最新版本源码编译的教程,所以我在编译过程中也遇到了很多坑.回过头来看,这些坑都是可以避免的,想要自己尝试编译的同学,可以根据本文快速的实现自己编译webkit(~除去下载文件的时间,15分钟准备环境,1小时编译完成). 下载最近源码 最近的下载版本和源码在这里: http://nightly.webkit.org/ 我使用的源码是这个版本built on 13 October 2014 and i

Android源码探究之AsyncTask 源码解析

AsyncTask源码使用 Api23版本,后面介绍和以前版本改动不同之处. 先看使用: /** * 下面四个方法中除了doInBackground方法在子线程,其他方法都在主线程执行 * String 表示传进来的参数, * Void 表示子线程执行过程中对主线程进行反馈所传的数据类型 * Integer 子线程执行的结果 */ private class MyAsyncTask extends AsyncTask<String,Void,Integer>{ @Override protec

Flink 源码解析 —— 如何获取 ExecutionGraph ?

https://t.zsxq.com/UnA2jIi 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1学习 -- Mac 上搭建 Flink 1.6.0 环境并构建运行简单程序入门 3.Flink 从0到1学习 -- Flink 配置文件详解 4.Flink 从0到1学习 -- Data Source 介绍 5.Flink 从0到1学习 -- 如何自定义 Data Source ? 6.Flink 从0到1学习 -- Data Sink 介绍 7