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脚本特性
Sep 13 Javascript
HTML长文本截取含有HTML代码同样适用的两种方法
Jul 31 Javascript
鼠标滑过出现预览的大图提示效果
Feb 26 Javascript
微信企业号开发之微信考勤百度地图定位
Sep 11 Javascript
jQuery 插件封装的方法
Nov 16 Javascript
Jquery Easyui日历组件Calender使用详解(23)
Dec 18 Javascript
Angular2使用Guard和Resolve进行验证和权限控制
Apr 24 Javascript
详解Angular 4 表单快速入门
Jun 05 Javascript
Vue入门之数据绑定(小结)
Jan 08 Javascript
Vue实现拖放排序功能的实例代码
Jul 08 Javascript
vue动画—通过钩子函数实现半场动画操作
Aug 09 Javascript
vue或react项目生产环境去掉console.log的操作
Sep 02 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
laravel框架select2多选插件初始化默认选中项操作示例
2020/02/18 PHP
accesskey 提交
2006/06/26 Javascript
dojo 之基础篇(二)之从服务器读取数据
2007/03/24 Javascript
解决火狐浏览器下JS setTimeout函数不兼容失效不执行的方法
2012/11/14 Javascript
使用jquery解析XML的方法
2014/09/05 Javascript
JQuery中DOM事件合成用法实例分析
2015/06/13 Javascript
jquery通过扩展select控件实现支持enter或focus选择的方法
2015/11/19 Javascript
JavaScript阻止回车提交表单的方法
2015/12/30 Javascript
不同js异步函数同步的实现方法
2016/05/28 Javascript
基于jQuery和hwSlider实现内容左右滑动切换效果附源码下载(一)
2016/06/22 Javascript
vue-loader教程介绍
2017/06/14 Javascript
详解JS数据类型的值拷贝函数(深拷贝)
2017/07/13 Javascript
关于JavaScript的单双引号嵌套问题
2017/08/20 Javascript
jquery实现限制textarea输入字数的方法
2017/09/06 jQuery
解决Layui中templet中a的onclick参数传递的问题
2019/09/20 Javascript
聊聊Vue中provide/inject的应用详解
2019/11/10 Javascript
vue keep-alive列表页缓存 详情页返回上一页不刷新,定位到之前位置
2019/11/26 Javascript
webpack的 rquire.context用法实现工程自动化的方法
2020/02/07 Javascript
微信小程序实现canvas分享朋友圈海报
2020/06/21 Javascript
深入源码解析Python中的对象与类型
2015/12/11 Python
Python分支结构(switch)操作简介
2018/01/17 Python
python递归函数绘制分形树的方法
2018/06/22 Python
python简单鼠标自动点击某区域的实例
2019/06/25 Python
Application Cache未缓存文件无法访问无法加载问题
2014/05/31 HTML / CSS
Nike台湾官方商店:Nike.com (TW)
2017/08/16 全球购物
灵泰克Java笔试题
2016/01/09 面试题
班会关于环保演讲稿
2013/12/29 职场文书
如何写一份好的自荐信
2014/01/02 职场文书
宠物店的创业计划书范文
2014/01/11 职场文书
特色蛋糕店创业计划书
2014/01/28 职场文书
销售人员自我评价
2014/02/01 职场文书
诚信承诺书范文
2014/03/27 职场文书
个人求职信范文
2014/05/24 职场文书
2015世界地球日活动总结
2015/02/09 职场文书
先进个人总结范文
2015/02/15 职场文书
详细了解java监听器和过滤器
2021/07/09 Java/Android