跟随 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标准不是某一个标准,而 ...
随机推荐
- [原创]首次制作JQueryUI插件-Timeline时间轴
特点: 1. 支持多左右滚动,左右拖动. 2. 时间轴可上下两种显示方式. 3. 支持两种模式的平滑滚动/拖动. 4. 行压缩(后续版本此处可设置是否开启,上传的代码不带这个功能). 5. 支持hov ...
- sql server几种读写分离方案的比较
在生产环境中我们经常会遇到这种情况: 前端的oltp业务很繁忙,但是需要对这些运营数据进行olap,为了不影响前端正常业务,所以需要将数据库进行读写分离. 这里我将几种可以用来进行读写分离的方案总结一 ...
- linux(64位的系统)下nasm进行汇编链接时出现的问题
出现问题: $nasm -f elf hello.asm -o hello.o $ld -s hello.o -o hello ld: i386 architecture of input file ...
- Vsphere初试——基本安装
现有工具: 一台Dell PowerEdge R820服务器 VMware-VMvisor-Installer-5.5.0.update01-1623387.x86_64(ESXi).iso VMwa ...
- JS入门学习,编写一个简易月历
//今天最头疼的地方在于 getElementsByClassName()的 [] ~~ //错了N遍后只能说有点点头绪,如果不加[] 查找的就是全部吧 加上[]能精确控制的标签或者class < ...
- 【2016-10-31】【坚持学习】【Day16】【MongoDB】【入门】
下载,安装: http://www.mongodb.org/downloads 命令行下运行 MongoDB 服务器 为了从命令提示符下运行MongoDB服务器,你必须从MongoDB目录的bin目录 ...
- AC日记——挤牛奶 洛谷 P1204
题目描述 三个农民每天清晨5点起床,然后去牛棚给3头牛挤奶.第一个农民在300秒(从5点开始计时)给他的牛挤奶,一直到1000秒.第二个农民在700秒开始,在 1200秒结束.第三个农民在1500秒开 ...
- The week in .NET - 1/12/2015
On.NET Last week, we had Mads Torgersen on the show to talk about language design in general, and C# ...
- Convertion of grey code and binary 格雷码和二进制数之间的转换
以下转换代码摘自维基百科 Wikipedia: /* The purpose of this function is to convert an unsigned binary number to r ...
- Windows phone应用开发[22]-再谈下拉刷新
几周之前在博客更新一篇Windows phone应用开发[18]-下拉刷新 博文,有很多人在微博和博客评论中提到了很多问题.其实在实际项目中我基于这篇博文提出解决问题思路优化了这个解决方案.为了能够详 ...