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 新浪背投广告实现代码
Jul 07 Javascript
dedecms页面如何获取会员状态的实例代码
Mar 15 Javascript
Js类的静态方法与实例方法区分及jQuery拓展的两种方法
Jun 03 Javascript
JS中对Cookie的操作详解
Aug 05 Javascript
jQuery实现底部浮动窗口效果
Sep 07 Javascript
AngularJS的ng Http Request与response格式转换方法
Nov 07 Javascript
jQuery 的 ready()的纯js替代方法
Nov 20 Javascript
JavaScript数据结构之二叉树的遍历算法示例
Apr 13 Javascript
本地搭建微信小程序服务器的实现方法
Oct 27 Javascript
Angular实现双向折叠列表组件的示例代码
Nov 21 Javascript
vue生成token并保存到本地存储中
Jul 17 Javascript
AngularJS动态生成select下拉框的方法实例
Nov 17 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
从零开始 教你如何搭建Discuz!4.1论坛
2006/07/07 PHP
php 随机排序广告的实现代码
2011/05/09 PHP
php中的注释、变量、数组、常量、函数应用介绍
2012/11/16 PHP
PHP实现批量清空删除指定文件夹所有内容的方法
2017/05/30 PHP
实用javaScript技术-屏蔽类
2006/08/15 Javascript
静态的动态续篇之来点XML
2006/08/15 Javascript
在网页中屏蔽快捷键
2006/09/06 Javascript
监控 url fragment变化的js代码
2010/04/19 Javascript
jQuery操作input type=radio的实现代码
2012/06/14 Javascript
JS远程获取网页源代码实例
2013/09/05 Javascript
JavaScript里四舍五入函数round用法实例
2015/04/06 Javascript
js获取表格的行数和列数的方法
2015/10/23 Javascript
JS鼠标拖拽实例分析
2015/11/23 Javascript
Bootstrap导航栏各元素操作方法(表单、按钮、文本)
2015/12/28 Javascript
Active控件问题小结(附解决办法)
2016/06/09 Javascript
Vue.js 2.0 和 React、Augular等其他前端框架大比拼
2016/10/08 Javascript
清除js缓存的多种方法总结
2016/12/09 Javascript
JavaScript和JQuery获取DIV值的方法示例
2017/03/07 Javascript
JS实现简单的选择题测评系统代码思路详解(demo)
2017/09/03 Javascript
详谈vue+webpack解决css引用图片打包后找不到资源文件的问题
2018/03/06 Javascript
js将日期格式转换为YYYY-MM-DD HH:MM:SS
2020/09/18 Javascript
jQuery实现二级导航菜单的示例
2020/09/30 jQuery
基于Vue.js+Nuxt开发自定义弹出层组件
2020/10/09 Javascript
如何在 ant 的table中实现图片的渲染操作
2020/10/28 Javascript
python 图片验证码代码分享
2012/07/04 Python
零基础写python爬虫之urllib2中的两个重要概念:Openers和Handlers
2014/11/05 Python
Python Flask基础教程示例代码
2018/02/07 Python
Python使用add_subplot与subplot画子图操作示例
2018/06/01 Python
Django中数据库的数据关系:一对一,一对多,多对多
2018/10/21 Python
利用python循环创建多个文件的方法
2018/10/25 Python
html5教程调用绘图api画简单的圆形代码分享
2013/12/04 HTML / CSS
Myprotein丹麦官网:欧洲第一运动营养品牌
2019/04/15 全球购物
门卫人员岗位职责
2013/12/24 职场文书
法学院毕业生求职信
2014/06/25 职场文书
客户答谢会活动方案
2014/08/31 职场文书
中秋节随笔
2015/08/15 职场文书