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 相关文章推荐
再谈IE中Flash控件的自动激活 ObjectWrap
Mar 09 Javascript
input输入框的自动匹配(原生代码)
Mar 19 Javascript
js截取字符串的两种方法及区别详解
Nov 05 Javascript
jQuery动画_动力节点节点Java学院整理
Jul 04 jQuery
js指定步长实现单方向匀速运动
Jul 17 Javascript
JavaScript实现二叉树定义、遍历及查找的方法详解
Dec 20 Javascript
vue-cli 脚手架基于Nightwatch的端到端测试环境的过程
Sep 30 Javascript
Vue实现将数据库中带html标签的内容输出(原始HTML(Raw HTML))
Oct 28 Javascript
Vue组件模板的几种书写形式(3种)
Feb 19 Javascript
JS错误处理与调试操作实例分析
Apr 13 Javascript
Postman内建变量常用方法实例解析
Jul 28 Javascript
vue 单页应用和多页应用的优劣
Oct 22 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
PHP实现的汉字拼音转换和公历农历转换类及使用示例
2014/07/01 PHP
PHP基于迭代实现文件夹复制、删除、查看大小等操作的方法
2017/08/11 PHP
一个很简单的办法实现TD的加亮效果.
2006/06/29 Javascript
JavaScript 原型与继承说明
2010/06/09 Javascript
javascript 运算数的求值顺序
2011/08/23 Javascript
防止浏览器记住用户名及密码的简单实用方法
2013/04/22 Javascript
js计算任意值之间随机数的方法
2015/01/16 Javascript
JQuery基础语法小结
2015/02/27 Javascript
JQuery实现带排序功能的权限选择实例
2015/05/18 Javascript
JavaScript 常见安全漏洞和自动化检测技术
2015/08/21 Javascript
jQuery事件绑定方法学习总结(推荐)
2016/11/21 Javascript
bootstrap下拉菜单使用方法解析
2017/01/13 Javascript
Ajax和Comet技术总结
2017/02/19 Javascript
JS关于刷新页面的相关总结
2018/05/09 Javascript
javascript实现弹幕墙效果
2019/11/28 Javascript
webpack+express实现文件精确缓存的示例代码
2020/06/11 Javascript
javascript实现图片轮换动作方法
2020/08/07 Javascript
vue-router 2.0 跳转之router.push()用法说明
2020/08/12 Javascript
Jquery $.map使用方法实例详解
2020/09/01 jQuery
vant-ui组件调用Dialog弹窗异步关闭操作
2020/11/04 Javascript
Python简单格式化时间的方法【strftime函数】
2016/09/18 Python
Python查看微信撤回消息代码
2018/06/07 Python
python读取Excel表格文件的方法
2019/09/02 Python
Python序列化pickle模块使用详解
2020/03/05 Python
python3 sleep 延时秒 毫秒实例
2020/05/04 Python
Css3新特性应用之形状总结
2016/12/08 HTML / CSS
美国最大的珠宝首饰网上商城:Jewelry.com
2016/07/22 全球购物
打架检讨书100字
2014/01/19 职场文书
网上开店必备创业计划书
2014/01/26 职场文书
2014年祖国生日寄语
2014/09/19 职场文书
论文答谢词
2015/01/20 职场文书
社区活动总结
2015/02/04 职场文书
升职感谢领导的话语及升职感谢信
2019/06/24 职场文书
利用javaScript处理常用事件详解
2021/04/14 Javascript
FP-growth算法发现频繁项集——发现频繁项集
2021/06/24 Python
css3中2D转换之有趣的transform形变效果
2022/02/24 HTML / CSS