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+XML 操作
Sep 20 Javascript
推荐dojo学习笔记
Mar 24 Javascript
JavaScript计算字符串中每个字符出现次数的小例子
Jul 02 Javascript
javaScript基础语法介绍
Feb 28 Javascript
AJAX和jQuery动态加载数据的实现方法
Dec 05 Javascript
设置jquery UI 控件的大小方法
Dec 12 Javascript
聊聊JavaScript如何实现继承及特点
Apr 07 Javascript
使用vue制作FullPage页面滚动效果
Aug 21 Javascript
js原生日历的实例(推荐)
Oct 31 Javascript
详解vue静态资源打包中的坑与解决方案
Feb 05 Javascript
Vue中Table组件Select的勾选和取消勾选事件详解
Mar 19 Javascript
微信公众号服务器验证Token步骤图解
Dec 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
如何使用动态共享对象的模式来安装PHP
2006/10/09 PHP
PHP PDO函数库详解
2010/04/27 PHP
php中突破基于HTTP_REFERER的防盗链措施(stream_context_create)
2011/03/29 PHP
php mssql扩展SQL查询中文字段名解决方法
2012/10/15 PHP
PHP小教程之实现双向链表
2014/06/12 PHP
php 三大特点:封装,继承,多态
2017/02/19 PHP
PHP的PDO预处理语句与存储过程
2019/01/27 PHP
[原创]保存的js无法执行的解决办法
2007/02/25 Javascript
关于javascript document.createDocumentFragment()
2009/04/04 Javascript
IE6,IE7,IE8下使用Javascript记录光标选中范围(已补全)
2011/08/28 Javascript
Jquery动态添加及删除页面节点元素示例代码
2014/06/16 Javascript
jQuery实现鼠标经过事件的延时处理效果
2020/08/20 Javascript
javascript常用函数(2)
2015/11/05 Javascript
JavaScript数值千分位格式化的两种简单实现方法
2016/08/01 Javascript
JavaScript计时器用法分析【setTimeout和clearTimeout】
2017/01/18 Javascript
jQuery点击弹出层弹出模态框点击模态框消失代码分享
2017/01/21 Javascript
微信小程序 动态绑定数据及动态事件处理
2017/03/14 Javascript
Vue2.0用户权限控制解决方案
2017/11/29 Javascript
js实现for循环跳过undefined值示例
2019/07/02 Javascript
Vue路由之JWT身份认证的实现方法
2019/08/26 Javascript
VSCode搭建Vue项目的方法
2020/04/30 Javascript
django使用图片延时加载引起后台404错误
2017/04/18 Python
Python学习小技巧之利用字典的默认行为
2017/05/20 Python
Python实现的矩阵类实例
2017/08/22 Python
python如何发布自已pip项目的方法步骤
2018/10/09 Python
利用python3筛选excel中特定的行(行值满足某个条件/行值属于某个集合)
2020/09/04 Python
python实现PolynomialFeatures多项式的方法
2021/01/06 Python
一款纯css3实现的非常实用的鼠标悬停特效演示
2014/11/05 HTML / CSS
领先的荷兰线上超市:荷兰之家Holland at Home(支持中文)
2021/01/21 全球购物
自荐信的五个重要部分
2013/10/29 职场文书
自荐信要包含哪些内容
2013/11/06 职场文书
同事打架检讨书
2014/02/04 职场文书
最美乡村医生事迹材料
2014/06/02 职场文书
观看《周恩来的四个昼夜》思想汇报
2014/09/12 职场文书
先进个人事迹材料范文
2014/12/30 职场文书
2016年元旦主持词
2015/07/06 职场文书