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 相关文章推荐
JavaScript的public、private和privileged模式
Dec 28 Javascript
JavaScript常用本地对象小结
Mar 28 Javascript
ionic 上拉菜单(ActionSheet)实例代码
Jun 06 Javascript
js入门之Function函数的使用方法【新手必看】
Nov 22 Javascript
微信小程序开发之toast提示插件使用示例
Jun 08 Javascript
通过button将form表单的数据提交到action层的实例
Sep 08 Javascript
基于js 各种排序方法和sort方法的区别(详解)
Jan 03 Javascript
Vue前端判断数据对象是否为空的实例
Sep 02 Javascript
JS pushlet XMLAdapter适配器用法案例解析
Oct 16 Javascript
vue $router和$route的区别详解
Dec 02 Vue.js
微信小程序APP页面的之间的相互传递参数以及自定义组件
Apr 19 Javascript
字节飞书面试promise.all实现示例
Jun 16 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
MySQL中create table语句的基本语法是
2007/01/15 PHP
ajax+php打造进度条 readyState各状态
2010/03/20 PHP
PHP 异步执行方法,模拟多线程的应用分析
2013/06/03 PHP
跨浏览器PHP下载文件名中的中文乱码问题解决方法
2015/03/05 PHP
PHP按指定键值对二维数组进行排序的方法
2015/12/22 PHP
PHP实现的激活用户注册验证邮箱功能示例
2017/06/06 PHP
window.js 主要包含了页面的一些操作
2009/12/23 Javascript
JavaScript中的toDateString()方法使用详解
2015/06/12 Javascript
js获取及判断键盘按键的方法
2015/12/01 Javascript
JavaScript中setTimeout的那些事儿
2016/11/14 Javascript
JavaScript实现Fly Bird小游戏
2016/12/15 Javascript
Javascript Event(事件)的传播与冒泡
2017/01/23 Javascript
javascript数据类型详解
2017/02/07 Javascript
JS中setTimeout和setInterval的最大延时值详解
2017/02/13 Javascript
详解vue组件化开发-vuex状态管理库
2017/04/10 Javascript
JS实现下拉菜单列表与登录注册弹窗效果
2017/08/10 Javascript
vue项目实现github在线预览功能
2018/06/20 Javascript
Vue-Quill-Editor富文本编辑器的使用教程
2018/09/21 Javascript
Vue官方推荐AJAX组件axios.js使用方法详解与API
2018/10/09 Javascript
JavaScript类型相关的常用操作总结
2019/02/14 Javascript
详解Vue源码中一些util函数
2019/04/24 Javascript
JS算法教程之字符串去重与字符串反转
2020/12/15 Javascript
Python编程修改MP3文件名称的方法
2017/04/19 Python
PyQt5每天必学之像素图控件QPixmap
2018/04/19 Python
kafka-python批量发送数据的实例
2018/12/27 Python
pandas读取CSV文件时查看修改各列的数据类型格式
2019/07/07 Python
python自动识别文本编码格式代码
2019/12/26 Python
可视化pytorch 模型中不同BN层的running mean曲线实例
2020/06/24 Python
AP澳洲中文网:澳洲正品直邮,包税收件无忧
2019/07/12 全球购物
Booking.com德国:预订最好的酒店和住宿
2020/02/16 全球购物
3个CCIE对一个工程师的面试题
2012/05/06 面试题
会计电算化专业毕业生求职信范文
2013/12/10 职场文书
电大本科自我鉴定
2014/02/05 职场文书
实习协议书范本
2014/04/22 职场文书
开展批评与自我批评发言材料
2014/10/17 职场文书
Python办公自动化PPT批量转换操作
2021/09/15 Python