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字符串连接性能问题有争议
Jan 12 Javascript
jQuery点击弹出下拉菜单的小例子
Aug 01 Javascript
jquery制作 随机弹跳的小球特效
Feb 01 Javascript
js实现兼容IE和FF的上下层的移动
May 04 Javascript
多个js毫秒倒计时同时进行效果
Jan 05 Javascript
Bootstrap每天必学之导航组件
Apr 25 Javascript
原生JS实现旋转木马式图片轮播插件
Apr 25 Javascript
JS加载iFrame出现空白问题的解决办法
May 13 Javascript
axios 处理 302 状态码的解决方法
Apr 10 Javascript
如何从零开始利用js手写一个Promise库详解
Apr 19 Javascript
Vue插件从封装到发布的完整步骤记录
Feb 28 Javascript
Vue发布订阅模式实现过程图解
Apr 30 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
深入Memcache的Session数据的多服务器共享详解
2013/06/13 PHP
PHP实现对站点内容外部链接的过滤方法
2014/09/10 PHP
PHP页面实现定时跳转的方法
2014/10/31 PHP
php post换行的方法
2020/02/03 PHP
通过JAVAScript实现页面自适应
2007/01/19 Javascript
利用Ext Js生成动态树实例代码
2008/09/08 Javascript
DOM下的节点属性和操作小结
2009/05/14 Javascript
2010年最佳jQuery插件整理
2010/12/06 Javascript
一个简单的网站访问JS计数器 刷新1次加1次访问
2012/09/20 Javascript
jquery通过select列表选择框对表格数据进行过滤示例
2014/05/07 Javascript
JQuery给网页更换皮肤的方法
2015/05/30 Javascript
使用CoffeeScrip优美方式编写javascript代码
2015/10/28 Javascript
总结JavaScript中布尔操作符||与&amp;&amp;的使用技巧
2015/11/17 Javascript
AngularJs Managing Service Dependencies详解
2016/09/02 Javascript
详解vue-validator(vue验证器)
2017/01/16 Javascript
js仿微博动态栏功能
2017/02/22 Javascript
提升页面加载速度的插件InstantClick
2017/09/12 Javascript
js中split()方法得到的数组长度问题
2018/07/19 Javascript
深入理解 Koa 框架中间件原理
2018/10/18 Javascript
js的继承方法小结(prototype、call、apply)(推荐)
2019/04/17 Javascript
Javascript通过控制类名更改样式
2019/05/24 Javascript
nodejs使用Sequelize框架操作数据库的实现
2020/10/21 NodeJs
Python 快速实现CLI 应用程序的脚手架
2017/12/05 Python
浅谈python的深浅拷贝以及fromkeys的用法
2019/03/08 Python
python实现微信小程序用户登录、模板推送
2019/08/28 Python
python全栈开发语法总结
2020/11/22 Python
收集的22款给力的HTML5和CSS3帮助工具
2012/09/14 HTML / CSS
HTML5仿手机微信聊天界面
2016/03/18 HTML / CSS
美国智能家居专家:tink
2019/06/04 全球购物
计算机大学生职业生涯规划书范文
2014/02/19 职场文书
《罗布泊,消逝的仙湖》教学反思
2014/03/01 职场文书
乡村卫生服务一体化管理实施方案
2014/03/30 职场文书
给校长的建议书300字
2014/05/16 职场文书
建筑施工安全生产责任书
2014/07/22 职场文书
法英专业大学生职业生涯规划范文:衡外情,量己力!
2014/09/23 职场文书
励志语录:时光飞逝,请学会珍惜所有的人和事
2020/01/16 职场文书