js中查找最近的共有祖先元素的实现代码


Posted in Javascript onDecember 30, 2010

先来看概念,首先DOM是一棵树,其根节点是Document,大致可以用下图来表示:
js中查找最近的共有祖先元素的实现代码
所谓“最近的共有祖先元素”,是指给定一系列元素,找出在树中深度最大的,但同时为所有这些元素的祖先元素的元素。

比如上图中,I和G的结果为C,G和H的结果为A,D和E的结果为html,C和B的结果为html等。

测试驱动
对于偏逻辑的题,并没有十足的把握函数是正确的,因此还是先构造测试的用命,力求让函数通过测试。

本次就以上图的结构作为DOM结构,A表示body,B表示head,其他节点均使用div元素,同时以上文中所说的作为测试的输入和输出,先构造一下测试:

function test() { 
var result; 
result = find('i', 'g'); 
result.id !== 'c' && alert('fail (i, g)'); 
result = find('g', 'h'); 
result.id !== 'a' && alert('fail (g, h)'); 
result = find('d', 'e'); 
result.nodeName.toLowerCase() !== 'html' && alert('fail (d, e)'); 
result = find('c', 'b'); 
result.nodeName.toLowerCase() !== 'html' && alert('fail (c, b)'); 
}

基本逻辑
这次的逻辑大致是这样的:

1、针对每个给定的元素,从父元素到document依次向上遍历。
2、对遍历过程中经过的每个元素,保存到一个有序的map中,以元素为键,以遍历到的次数为值。
2、最后遍历map,找同第一个值与给定元素个数相同的项,就是第一个被所有元素的遍历都经过的元素,也即最近的共同祖先元素了。
细节问题
在实际过程中,对map的构建比较重要,这里涉及到2个问题:

1、map不能直接以元素作为键,必须转换为合适的基元类型(如number, string, Regex等)。
2、chrome对object中的键会有自动排序,因此尽量避免使用number类型作为键。

对于第一个问题,必须给元素绑定一个合适的字段,起到唯一性标识符的作用。好在HTML5提供了data-*属性,使DOM的元数据承载能力有了很大的提高,可以大胆地添加希望的属性了。

对于第二个问题,本身也不难,生成的标识符避开number就行了,方便的方法是加个下划线,或者使用String.fromCharCode转成字符,无论怎么样都无所谓。
实现代码
代码有点长,主要是个人比较喜欢偏JAVA的风格,每一个语句每一个分支都清清楚楚,不喜欢用&&或者||来处理条件分支,所以有很多行只有一个大括号之类的情况,其实真正有效的代码还是精简的。懒得装类似toggle之类的插件,也不想看到滚动条,就随便扔在这了。

function find() { 
var length = arguments.length, 
i = 0, 
node, //当前节点 
parent, //父节点 
counter = 0, 
uuid, //给DOM的唯一标识符 
hash = {}; //最后结果的map //对每一个元素,向上遍历至document 
//这个双层的循环是不可避免的 
for (; i < length; i++) { 
//获取node 
node = arguments[i]; 
if (typeof node == 'string') { 
node = document.getElementById(node); 
} 
//向上遍历 
while (parent = node.parentElement || node.parentNode) { 
//到document就停下来,不然就是死循环 
if (parent.nodeType == 9) { 
break; 
} 
//获取或添加一下标识符 
uuid = parent.getAttribute('data-find'); 
if (!uuid) { 
uuid = '_' + (++counter); //避免chrome对hash重排序 
parent.setAttribute('data-find', uuid); 
} 
//增加计数 
if (hash[uuid]) { 
hash[uuid].count++; 
} 
else { 
hash[uuid] = {node: parent, count: 1}; 
} 
node = parent; 
} 
} 
//hash中只存有各节点向上遍历经过的父节点,不应该很大 
//因此这个循环是比较快的 
for (i in hash) { 
if (hash[i].count == length) { 
return hash[i].node; 
} 
} 
};

点评
测试没问题,但测试用例是否完善实在不好说,期待网友帮我找出问题来,对于逻辑型的实在是没啥自信说100%没问题。
对于取父元素,习惯性兼容IE写成parentElement || parentNode,这是因为IE的一个BUG,当一个元素刚被创建出来但未进入DOM时,parentNode是不存在的。不过本次函数保证节点在DOM树中,其实parentElement就没有必要了。所以有时候习惯性的兼容代码也不见得就一定是好事,最适合当前环境的代码才是好代码。
虽然说2重循环不可避免,但总隐隐感觉循环还是有办法在特定情况下少做点事,比如向上遍历的时候发现某个元素已经不可能是所有元素的共有祖先了,那么就不要再去递增count值了。
最后的for..in循环有没有办法省掉呢?在上面的2重循环中有没有办法实时地就通过一个变量始终保存最合适的节点呢?
Javascript 相关文章推荐
JS创建优美的页面滑动块效果 - Glider.js
Sep 27 Javascript
JavaScript Chart 插件整理
Jun 18 Javascript
使用javascript实现雪花飘落的效果
Jan 13 Javascript
C#中使用迭代器处理等待任务
Jul 13 Javascript
基于Bootstrap的Metronic框架实现条码和二维码的生成及打印处理操作
Aug 29 Javascript
JS获取html元素的标记名实现方法
Oct 08 Javascript
微信小程序 购物车简单实例
Oct 24 Javascript
Vue.js中用v-bind绑定class的注意事项
Dec 13 Javascript
浅谈sass在vue注意的地方
Aug 10 Javascript
浅谈JsonObject中的key-value数据解析排序问题
Dec 06 Javascript
Angular @HostBinding()和@HostListener()用法
Mar 05 Javascript
vue动态路由配置及路由传参的方式
May 23 Javascript
Js 弹出框口并返回值的两种常用方法
Dec 30 #Javascript
JavaScript之appendChild、insertBefore和insertAfter使用说明
Dec 30 #Javascript
Javascript事件热键兼容ie|firefox
Dec 30 #Javascript
某人初学javascript的时候写的学习笔记
Dec 30 #Javascript
Javascript延迟执行实现方法(setTimeout)
Dec 30 #Javascript
JavaScript的document对象和window对象详解
Dec 30 #Javascript
javascript hashtable 修正版 下载
Dec 30 #Javascript
You might like
Zend 输出产生XML解析错误
2009/03/03 PHP
PHP使用Alexa API获取网站的Alexa排名例子
2014/06/12 PHP
ThinkPHP提交表单时默认自动转义的解决方法
2014/11/25 PHP
PHP中的访问修饰符简单比较
2019/02/02 PHP
laravel框架模板之公共模板、继承、包含实现方法分析
2019/08/30 PHP
第一个JavaScript入门基础 document.write输出
2010/02/22 Javascript
jQuery 点击图片跳转上一张或下一张功能的实现代码
2010/03/12 Javascript
鼠标右击事件代码(asp.net后台)
2011/01/27 Javascript
ASP.NET jQuery 实例8 (动态添加内容到DropDownList)
2012/02/03 Javascript
js添加table的行和列 具体实现方法
2013/07/22 Javascript
js二维数组定义和初始化的三种方法总结
2014/03/03 Javascript
javascript实现控制文字大中小显示
2015/04/28 Javascript
Backbone.js框架中Model与Collection的使用实例
2016/05/07 Javascript
通过网页查看JS源码中汉字显示乱码的解决方法
2016/10/26 Javascript
浅谈JavaScript中promise的使用
2017/01/11 Javascript
JavaScript两个变量交换值的实现方法
2017/03/01 Javascript
JS创建Tag标签的方法详解
2017/06/09 Javascript
想用好React的你必须要知道的一些事情
2017/07/24 Javascript
微信小程序学习笔记之文件上传、下载操作图文详解
2019/03/29 Javascript
[03:03]2014DOTA2国际邀请赛 EG战队专访
2014/07/12 DOTA
Python编程中用close()方法关闭文件的教程
2015/05/24 Python
利用Python读取txt文档的方法讲解
2018/06/23 Python
python3.7.0的安装步骤
2018/08/27 Python
python实现点击按钮修改数据的方法
2019/07/17 Python
python中类的输出或类的实例输出为这种形式的原因
2019/08/12 Python
Django中密码的加密、验密、解密操作
2019/12/19 Python
python GUI库图形界面开发之PyQt5信号与槽的高级使用技巧(自定义信号与槽)详解与实例
2020/03/06 Python
英国打印机墨水和碳粉商店:Printerinks
2017/06/30 全球购物
英国太阳镜品牌:Taylor Morris Eyewear
2018/04/18 全球购物
英国最大的滑板品牌选择:Route One
2019/09/22 全球购物
希腊品牌鞋类销售网站:epapoutsia.gr
2020/03/18 全球购物
致200米运动员广播稿
2014/02/06 职场文书
庆元旦文艺演出主持词
2014/03/27 职场文书
保护环境倡议书500字
2014/05/19 职场文书
党员干部对十八届四中全会的期盼
2014/10/17 职场文书
预备党员个人总结
2015/02/14 职场文书