跟随标准与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 基本一致
注意点
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里。
跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByName的更多相关文章
- 跟随标准与Webkit源码探究DOM -- 获取元素之querySelector,querySelectorAll
使用CSS选择器获取元素 -- querySelector,querySelectorAll(HTML5) 标准 W3C Selector API Level 1为Document,DocumentF ...
- 跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByClassName
按照类名获取元素 -- getElementsByClassName(HTML5) 标准 WHATWG 在Document与Element上均有定义,原型 HTMLCollection getElem ...
- 跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByTagName
按照标签名获取元素 -- getElementsByTagName 标准 DOM 1在Element和Document两个interface中均有定义,原型NodeList getElementsBy ...
- 跟随标准与Webkit源码探究DOM -- 获取元素之getElementById
按照ID获取元素 -- getElementById 标准 DOM 1,定义在HTMLDocument Interface 中,原型Element getElementById(in DOMStrin ...
- ConcurrentHashMap源码探究 (JDK 1.8)
很早就知道在多线程环境中,HashMap不安全,应该使用ConcurrentHashMap等并发安全的容器代替,对于ConcurrentHashMap也有一定的了解,但是由于没有深入到源码层面,很多理 ...
- Vue源码探究-虚拟DOM的渲染
Vue源码探究-虚拟DOM的渲染 在虚拟节点的实现一篇中,除了知道了 VNode 类的实现之外,还简要地整理了一下DOM渲染的路径.在这一篇中,主要来分析一下两条路径的具体实现代码. 按照创建 Vue ...
- Vue源码探究-全局API
Vue源码探究-全局API 本篇代码位于vue/src/core/global-api/ Vue暴露了一些全局API来强化功能开发,API的使用示例官网上都有说明,无需多言.这里主要来看一下全局API ...
- Vue源码探究-事件系统
Vue源码探究-事件系统 本篇代码位于vue/src/core/instance/events.js 紧跟着生命周期之后的就是继续初始化事件相关的属性和方法.整个事件系统的代码相对其他模块来说非常简短 ...
- Vue源码探究-状态初始化
Vue源码探究-状态初始化 Vue源码探究-源码文件组织 Vue源码探究-虚拟DOM的渲染 本篇代码位于vue/src/core/instance/state.js 继续随着核心类的初始化展开探索其他 ...
随机推荐
- Java经典类库-Guava中的函数式编程讲解
如果我要新建一个java的项目,那么有两个类库是必备的,一个是junit,另一个是Guava.选择junit,因为我喜欢TDD,喜欢自动化测试.而是用Guava,是因为我喜欢简洁的API.Guava提 ...
- LEA指令
格 式:LEA OPRD1,OPRD2 功 能:将有效地址传送到指定的的寄存器 OPRD1 为目的操作数,可为任意一个16位的通用寄存器. OPRD2 为源操作数,可为变量名.标号或地址表 ...
- js关于事件
摘要:事件在Web前端领域有很重要的地位,很多重要的知识点都与事件有关.本文旨在对常用的事件相关知识做一个汇总和记录. 在前端中,有一个很重要的概念就是事件.我对于事件的理解就是使用者对浏览器进行的一 ...
- paip.互联网产品要成功的要素
paip.互联网产品要成功的要素 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog.csdn.net/atti ...
- 祸福相依,大难之后的O2O迎来新福报?
今天的O2O似乎已经成为了一个人人都不愿意提的名词,很多原本做O2O的创业者,如今都不提自己是O2O,只说是互联网+.创业者们实际上仍然是在干着O2O的事情,之所以不敢提不愿提,无非就是一提O2O,投 ...
- 将外卖O2O广告一棍子打成竞价排名,秤把平了吗?
近日,诸多媒体报道称美团外卖.饿了么等外卖O2O将竞价排名引入外卖平台当中进行广告运营一事闹得沸沸扬扬.那么,美团外卖.饿了么真的都是竞价排名吗? 其实,美团外卖的付费推广仅仅只是针对列表的固定位置, ...
- iOS-项目打包为ipa文件
最近自己做的一个项目,由于app store发布流程比较复杂,且审核周期较长,客户希望提前能看到产品,所以我先给自己的项目打包成一个ipa文件(类似Android的apk安装包),然后发布在" ...
- cmd命令生成android签名证书
cmd命令生成android签名证书,有空在写一篇eclipse导出带签名的apk,这里面包括生成新的签名.现在还是讲讲在cmd怎么操作生成签名证书. 1.dos下进入JDK的bin目录 运行如下命令 ...
- Maven学习总结(一)——Maven入门——转载
一.Maven的基本概念 Maven(翻译为"专家","内行")是跨平台的项目管理工具.主要服务于基于Java平台的项目构建,依赖管理和项目信息管理. 1.1. ...
- 扩展easyui 的表单验证
easyui 的validatebox()提供了自定义验证的方法,为此我把一些常用的数据验证汇总了一下,代码如下: 代码 $.extend($.fn.validatebox.defaults.rule ...