跟随 Web 标准探究DOM -- Node 与 Element 的遍历
写在前面
这篇没有什么 WebKit 代码的分析,因为……没啥好分析的,在实现里无非就是树的(先序DFS)遍历而已,囧哈哈哈……在WebCore/dom/Node.h , WebCore/dom/ContainerNode.h 和 WebCore/dom/Element.h 以及对应的 .cpp 里看两眼就行了。下面这些属性一般都作为了私有变量直接放在了对象里(按照命名规范基本都叫m_xxx),然后通过和标准同名的 public 方法返回。不过要注意一下它们放在了哪里,比如Node里和子节点相关的方法一般定义到了 ContainerNode.h,Node 里需要意识到 Element 存在的方法一般放去了 Element.h (即使定义时是Node::xxx 这样的)。
这篇主要分析一下对作为 Node 的元素和作为 Element 的元素进行遍历的不同,以及总结一下各浏览器对这些 API 的兼容性。
Node 的遍历
Node 继承 EventTarget,Document,DocumentFragment,Element继承Node,所以下面提到的属性Document,DocumentFragment,Element都可以用。
Node.parentNode
标准
DOM 1定义在 Node interface,原型readonly attribute Node parentNode,指明Document,DocumentFragment,Attr 和不在树中的 node 的 parentNode 为 null。
DOM 2,DOM 3,WHATWG,DOM 4 都和 DOM 1 一致
注意点
这是一个只读属性,所以不能通给一个元素的parentNode赋值来移动它,任何对这个引用的赋值操作都会被无视。比如:
node.parentNode = anotherNode;
console.log(node.parentNode === anotherNode); // false
但是你可以修改它的parentNode的属性。
node.parentNode.title = "foo";
console.log(node.parentNode.title); // foo
此外,Document 和 Attr 没有parentNode还好理解,但是 Attr 没有就有点不好理解了,而且Entity 和Notation也是没有的 —— 反向理解,Node.childNodes也是不算 attribute node,entity node 之类的,人家不把你当孩子,你也没必要把人家当父母。
没有 parent 的 Node(比如刚刚用createElement创建或者用removeChild删除)的这个属性是 null。
兼容性
IE8- 里的 parentNode 有几个 bug:
新创建的元素的 parentNode 是 null,但修改过内容(比如用innerHTML或者appendChild)之后就会变成 DocumentFragment
var foo = document.createElement('div');
console.log(foo.parentNode); // null
foo.innerHTML = "bar"
console.log(foo.parentNode); // [object HTMLDocument]
console.log(foo.parentNode.nodeType); // 11 = DocumentFragment
从文档中删掉的节点,parentNode是DocumentFragment。对如下 HTML:
<div id="foo">
<div id="bar"></div>
</div>
执行 JS:
var foo = document.getElementById('bar');
console.log(foo.parentNode); // [object HTMLDivElement]
foo.parentNode.removeChild(foo);
console.log(foo.parentNode); // [object HTMLDocument]
console.log(foo.parentNode.nodeType); // 11 = DocumentFragment
Node.firstChild 和 Node.lastChild
标准
DOM 1(firstChild,lastChild)定义在 Node interface,原型readonly attribute Node firstChild和readonly attribute Node lastChild,指明Document,DocumentFragment,Attr 和不在树中的 node 的 parentNode 为 null。
DOM 2(firstChild,lastChild),DOM 3(firstChild,lastChild), WHATWG (firstChild,lastChild),DOM 4(firstChild,lastChild) 和 DOM 1 一致
注意点
这是一个只读属性,和parentNode一样是不能重新赋值的。
注意浏览器可能(而且很多都)将 text node 和 comment node 算在一个 node 的 child nodes 里(HTML 文本里的缩进和断行都会算成新的 text node 夹杂在元素之间),并且 document.firstNode 可能是 doctype,因此不能判定 firstChild 返回的是一个元素,如果想得到第一个元素的话,需要手动检查nodeType并往后过滤。
CSS pseudo element 不会被算入。
W3C FAQ 解释了为什么有 DOM 的实现会将空白字符算作 text node:
DOM 必须将处理过的 XML (且为了方便,很多 DOM 的实现会将 XML 与 HTML 的许多处理合并)原文全部交给应用,空白字符也不能丢掉(这样 DOM 树与 XML 文本才能完成一一映射),那么就应该找个类型的 node 将它塞进去了 -- 最合适的就是 text node。
兼容性
IE 8- 不将空白的 text node 算作子节点,IE 9+及其他浏览器都算。对如下HTML:
<div id="foo"> </div>
执行 JS:
var foo = document.getElementById('foo');
console.log(foo.firstChild); // null in IE8-, supposed be a text node
Node.nextSibling 和 Node.previousSibling
标准
DOM 1(previousSibling,nextSibling)定义在 Node interface,原型readonly attribute Node previousSibling和readonly attribute Node nextSibling,不存在对应 node 的返回 null。
DOM 2(previousSibling,nextSibling),DOM 3(previousSibling,nextSibling)和 DOM 1 一致。
WHATWG (previousSibling,nextSibling) 和 W3C DOM 一致,另外说明了 sibling 的概念 和 树中相对位置的概念(按照tree order,即先序DFS)
DOM 4(previousSibling,nextSibling)和 WHATWG 一致。
注意点
和Node.firstChild 与 Node.lastChild的注意事项类似。
兼容性
IE 8- 不将空白的 text node 算作 sibling,IE 9+及其他浏览器都算。
HTML:
<div></div> <div id="foo"></div>
JS:
var foo = document.getElementById('foo');
// [object HTMLDivElement] in IE8-, supposed to be a text node
console.log(foo.previousSibling);
Node.childNodes
标准
DOM 1定义在 Node interface,原型readonly attribute NodeList childNodes,指明了返回的 NodeList 是 live 的,且如果没有子节点时返回空的 NodeList.
WHATWG 原型 [SameObject] readonly attribute NodeList childNodes,和 W3C DOM 一致。DOM 4 和 WHATWG 一样。
注意点
和Node.firstChild 与 Node.lastChild的注意事项类似。返回的NodeList元素是只读的(可以改元素属性,不可以改引用)。要增删子元素的话对childNodes动脑筋是没用的……(注意:其他浏览器对childNodes中引用的修改仅仅是无视,但 IE 会怒报错)
HTML:
<div id="foo"><p></p></div>
JS:
var foo = document.getElementById('foo');
console.log(foo.childNodes.length); //
var bar = document.createElement('div');
foo.childNodes[0] = bar; // attempt to replace a child, throws error in IE
console.log(foo.childNodes[0].nodeName); // "P", not replaced
foo.childNodes[1] = bar; // attempt to add a child, throws error in IE
console.log(foo.childNodes.length); // 1, not added
delete foo.childNodes[0]; // attempt to delete a child, throws error in IE
console.log(foo.childNodes.length); // 1, not deleted
一般document.childNodes 只有 doctype 和 <html>元素,除非原文两者之间有注释。
元素的排列顺序是 document order,即按照 DOM 树中的先序 DFS 排列。
兼容性
IE 8- 不将空白的 text node 算作子节点,IE 9+及其他浏览器都算。
HTML:
<div id="foo"> </div>
JS:
var foo = document.getElementById('foo');
// 0 in IE8-, supposed to be 1
console.log(foo.childNodes.length);
Element 的遍历
Element 与 Node 的区别在于 Element 不包括 text node,comment node,etc. 实际上,Element 继承自 Node,也就是说它本来就是 Node 的一种。Element 都具备(或者说,应该具备) Node.nodeType == Node.ELEMENT_NODE 这个特性(还有其他哪几种nodeType参阅WHATWG标准,这里先不展开叙述)。以下的几种 API 可以看成 Node 版的 API 加上对结果进行Node.nodeType == Node.ELEMENT_NODE过滤(实际上 WebKit 的实现也基本都是这样干的)。
注意作为 Element 的遍历 API 基本都属于 HTML5 的新特性,W3C 标准里一般都只能在 DOM 4 里找到。
Node.parentElement
标准
WHATWG 将 parentElement 定义在了 Node ,原型readonly attribute Element? parentElement。W3C DOM 4 也一样。
乍一看,定义在Node似乎有点怪,不过仔细一想其实是很合理的 —— Element 的子节点不一定是 Element,譬如 text node。你不能阻碍人家寻亲的能力啊 :D
注意点
如果 Node 的父元素不是 Element,返回的是 null。
兼容性
实际上 parentElement 一开始是 IE 特有的(起码从 IE6 开始就有了),但 IE 仅为 Element 定义了这个属性(即是说 text node 之类的是不能用的)。此后这个属性进入了标准,目前基本各大浏览器都支持它,主要的兼容性问题出现在 IE 不支持非 Element 的 Node 使用这个属性。如果仅对 Element 使用它的话,是可以放心用的。
此外由于 IE8- 中 parentNode 有不轻的 bug(见前文),在只需要 Element 的场景下,可能用 parentElement 是更好的选择。
ParentNode.firstElementChild 和 ParentNode.lastElementChild
标准
目前 WHATWG 将 firstElementChild和lastElementChild 定义在了 ParentNode,原型为
readonly attribute Element? firstElementChild;
readonly attribute Element? lastElementChild;
它们原本在ElementTraversal,后来为了降低耦合,WHATWG 将 ElementTraversal 按照功能分割成了两个 interface ParentNode,ChildNode,而 firstElementChild和lastElementChild自然就挪去了针对有子元素的Node设置的ParentNode。
目前继承 ParentNode 的包括Document,Element 和 DocumentFragment,所以这三个 interface 的对象是可以访问firstElementChild和lastElementChild的。
W3C DOM4 和 WHATWG 一致,但是注意 DOM4 目前还不是 W3C Recommendation。目前处于 W3C Recommendation 状态的标准里, firstElementChild和lastElementChild仍然定义在 ElementTraversal。按照 Element Traversal 标准的规定,所有的 Element 都必须实现 ElementTraversal,但对其他 interface 不作要求。
因此,这两个属性在 WHATWG 和 W3C 的标准里存在分歧:WHATWG 标准中,Document,Element 和 DocumentFragment 均有这两个属性;W3C 标准中,目前仅有 Element 具有这两个属性。但因为和 WHATWG 一致的 DOM4 将来很有可能成为 W3C Recommendation,W3C 标准最后很有可能会和 WHATWG 一样,三种对象均有这两个属性。
注意点
如果没有子元素,返回的是 null。这两个属性也是只读的,可以在子元素上修改它的属性,但不可更改引用(会被无视)。
兼容性
由于属于较新的 API,在Element上的使用要 IE 9+ 才支持,其他浏览器的现行版本都有支持。
因为在 WHATWG 和 W3C 的现行标准里存在分歧,Document 和 DocumentFragment 对这两个属性的支持在各浏览器中不太一致。偏 WHATWG 的 Chrome,Firefox 和 Opera 支持 Document,Element 和 DocumentFragment,IE 9+ 和 Safari 仅支持 Element。考虑到 DOM4 将来应该会成为 W3C Recommendation,最后应该是三个 interface 都能支持的(当然,IE 就不能指望旧版本支持了……)
NonDocumentTypeChildNode.nextElementSibling 和 NonDocumentTypeChildNode.previousElementSibling
标准
在 WHATWG 标准里,和为了照顾 jQuery 兼容性而为getElementById 专门设一个 NonElementParentNode (而不是ParentNode)类似,为了照顾现存网页的兼容性,nextElementSibling 和 previousElementSibling 被定义在了一个专门分出来的 NonDocumentTypeChildNode(而不是ChildNode)里,参见 bug tracker上的讨论。
目前 NonDocumentTypeChildNode 的定义如下:
[NoInterfaceObject]
interface NonDocumentTypeChildNode {
readonly attribute Element? previousElementSibling;
readonly attribute Element? nextElementSibling;
};
Element implements NonDocumentTypeChildNode;
CharacterData implements NonDocumentTypeChildNode;
注:目前 WHATWG 标准里 ParentNode,NonElementParentNode,ChildNode 和 NonDocumentTypeChildNode 之间的关系如下图:

W3C DOM4 与 WHATWG 一致,但与ParentNode.firstElementChild 和 ParentNode.lastElementChild的情况类似的是,按照目前处于 W3C Recommendation 的 Element Traversal 的定义,只有 Element 拥有这两个属性,CharacterData 没有。
注意点
类似 ParentNode.firstElementChild 和 ParentNode.lastElementChild。
兼容性
也与 ParentNode.firstElementChild 和 ParentNode.lastElementChild类似,需要 IE9+。Chrome,Firefox 和 Opera 支持 Element 和 CharacterData上访问这两个属性,IE 9+ 和 Safari 仅支持 Element, 如果 W3C DOM 4 进入 Recommendation,很可能会统一。
ParentNode.childElementCount
标准
WHATWG / DOM4 定义在 ParentNode,原型readonly attribute unsigned long childElementCount。W3C Recommendation 里目前定义在 ElementTraversal,原型和 WHATWG 一样。
注意点
在符合标准的实现里,约等于 container.children.length。
兼容性
和 ParentNode.firstElementChild 的情况类似,需要 IE9+,Chrome,Firefox 和 Opera 支持 Document,Element 和 DocumentFragment,IE 9+ 和 Safari 仅支持 Element。
ParentNode.children
标准
虽然这个 API 很早就存在,但直到最近才标准化。WHATWG / DOM4 定义在ParentNode,原型[SameObject] readonly attribute HTMLCollection children,指明是一个 live 的 HTMLCollection 而不是NodeList,也就是说元素必然全是 Element(历史遗留问题带来的囧命名,和Node那边的名字对不上号,不叫childElements而叫children,不叫ElementList而叫HTMLCollection……)。
注意点
类似 Node.childNodes,得到的 HTMLCollection 是 live 且(引用)只读的。
兼容性
该属性最早出现在 IE 中,IE6 开始具备这个属性。此后各大浏览器跟着实现,Firefox是最后一个实现这个属性的主要浏览器(3.5开始,也蛮久了)。但是由于 WHATWG 标准的接受度不同,Chrome,Firefox 和 Opera 在支持 Document,Element 和 DocumentFragment上使用该属性,IE 和 Safari 仅支持 Element。 Chrome 和 Firefox 还实验性地支持在 SVGElement 上使用该属性。
另外,IE8- 的 children 会包含 comment node。
HTML:
<div id="foo"><!-- comment --></div>
JS:
var foo = document.getElementById('foo');
console.log(foo.children.length); // 1, supposed to be 0
跟随 Web 标准探究DOM -- Node 与 Element 的遍历的更多相关文章
- 【转载】跟随 Web 标准探究DOM -- Node 与 Element 的遍历
跟随 Web 标准探究DOM -- Node 与 Element 的遍历 这个是 Joyee 2014年更新的,可能是转战github缘故,一年多没有跟新了.这篇感觉还挺全面,就转载过来,如以前文章一 ...
- 跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByClassName
按照类名获取元素 -- getElementsByClassName(HTML5) 标准 WHATWG 在Document与Element上均有定义,原型 HTMLCollection getElem ...
- 跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByName
按照name属性获取多元素 -- getElementsByName 标准 DOM 1 定义在HTMLDocument Interface 中,原型NodeList getElementsByName ...
- 跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByTagName
按照标签名获取元素 -- getElementsByTagName 标准 DOM 1在Element和Document两个interface中均有定义,原型NodeList getElementsBy ...
- POPTEST培训:web自动化测试之DOM
POPTEST培训:web自动化测试之DOM poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq ...
- web自动化:DOM对象
一. 什么是DOM对象 定义:DOM(Document Object Mode,文档对象模型)是一套web标准,定义了访问HTML文档的一套属性.方法和事件 本质:网页与脚本语言沟通的桥梁.脚本语言通 ...
- web标准之道——笔记
字体设置 sans和sans-serif为通用字体,具体哪个字体被最终应用由浏览器决定,通用字体只有在其他字体都无效时才会被当作代替方案.通用字体应该放在最后面 sans衬线字体 容易阅读,一般使用在 ...
- [转]ExtJs基础--Html DOM、Ext Element及Component三者之间的区别
要学习及应用好Ext框架,必须需要理解Html DOM.Ext Element及Component三者之间的区别. 每一个HTML页面都有一个层次分明的DOM树模型,浏览器中的所有内容都有相应的DOM ...
- 深入理解Web标准(网站标准)
深入理解Web标准(网站标准) 我觉得一名Web前端应该好好理解Web标准到底是什么,为什么要在我们的实际实践中遵循Web标准. 什么是Web标准.百度百科的解释是: WEB标准不是某一个标准,而 ...
随机推荐
- 通过RTMP play分析FLV格式详解
最近做了一个rtmp中转服务程序,通过实践,熟悉rtmp play和push中各类格式,这里总结一下. 程序github地址: https://github.com/runner365/rtmp_re ...
- django url路径与模板中样式相对路径的问题
static目录下有css和js及image等文件夹,里面放置网站的一些静态文件,static位于网站根目录下,django中配置静态文件这个就细说,网上都有,昨天在添加新内容时发现一个问题,我的ur ...
- UVALive 4998 Simple Encryption --DFS
题意: 给出K1,求一个12位数(不含前导0)K2,使得K1^K2 mod (10^12) = K2. 解法: 求不动点问题. 有一个性质: 如果12位数K2满足如上式子的话,那么K2%1,K2%10 ...
- POJ1336 The K-League[最大流 公平分配问题]
The K-League Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 715 Accepted: 251 Descri ...
- HDU2089 不要62[数位DP]
不要62 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- 第7章 权限管理(2)_文件特殊权限(SUID、SGID、SBIT)
2. 文件特殊权限(主要用来临时提升命令执行者或其组身份) 2.1 SetUID (1)SetUID的功能 ①只有可以执行的二进制程序才能设定SUID权限.用来临时提升执行程序(或某条命令)的用户身份 ...
- 第3章 Linux常用命令(3)_文件搜索命令
3. 文件搜索命令 3.1 文件搜索:find (1)find命令 命令名称 find 命令所在路径 /bin/find 执行权限 所有用户 语法 find [搜索范围] [-选项] [匹配条件] - ...
- WebApi2跨域问题
一.跨域问题产生的原因:同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能. 现在所有支持JavaScript 的浏览器都会使用这个策略. 所谓同源是指,域 ...
- Nginx负载均衡实践之一:基本实现
由于现在的网站架构越来越大,基于互联网的用户也是日渐增长,所以传统的单机版服务器已经渐渐不能适应时代发展的需要.最近在和其他企业接触的过程中,发现对于互联网的经验尤为看重,所谓的互联网经验,其实就是指 ...
- 调用webapi 错误:使用 HTTP 谓词 POST 向虚拟目录发送了一个请求,而默认文档是不支持 GET 或 HEAD 以外的 HTTP 谓词的静态文件。的解决方案
第一次调用webapi出错如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http:// ...