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面向对象编程之如何实现方法重载
Jul 02 Javascript
js实现简单div拖拽功能实例
May 12 Javascript
基于Jquery和html5的7款个性化地图插件
Nov 17 Javascript
基于javascript实现动态时钟效果
Aug 18 Javascript
JavaScript获取客户端IP的方法(新方法)
Mar 11 Javascript
bootstrap table分页模板和获取表中的ID方法
Jan 10 Javascript
js实现密码强度检验
Jan 15 Javascript
通过js控制时间,一秒一秒自己动的实例
Oct 25 Javascript
JavaScript通过mouseover()实现图片变大效果的示例
Dec 20 Javascript
vue数据初始化initState的实例详解
Apr 11 Javascript
layui给下拉框、按钮状态、时间赋初始值的方法
Sep 10 Javascript
解决layUI的页面显示不全的问题
Sep 20 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 变量未定义等错误的解决方法
2011/01/12 PHP
php面向对象值单例模式
2016/05/03 PHP
session 加入redis的实现代码
2016/07/15 PHP
模仿JQuery.extend函数扩展自己对象的js代码
2009/12/09 Javascript
禁止IE用右键的JS代码
2013/12/30 Javascript
JS实现一个列表中包含上移下移删除等功能
2014/09/24 Javascript
js实现input框文字动态变换显示效果
2015/08/19 Javascript
jQuery模仿阿里云购买服务器选择购买时间长度的代码
2016/04/29 Javascript
javascript的replace方法结合正则使用实例总结
2016/06/16 Javascript
Node.js 日志处理模块log4js
2016/08/28 Javascript
Javascript的动态增加类的实现方法
2016/10/20 Javascript
JS多物体实现缓冲运动效果示例
2016/12/20 Javascript
bootstrap实现的自适应页面简单应用示例
2017/03/09 Javascript
webpack学习教程之publicPath路径问题详解
2017/06/17 Javascript
详解如何在vue中使用sass
2017/06/21 Javascript
ajax请求+vue.js渲染+页面加载的示例
2018/02/11 Javascript
详解javascript中的变量提升和函数提升
2018/05/24 Javascript
简述JS浏览器的三种弹窗
2018/07/15 Javascript
详解JavaScript添加给定的标签选项
2018/09/17 Javascript
一次Webpack配置文件的分离实战记录
2018/11/30 Javascript
Node.js 实现简单的无侵入式缓存框架的方法
2019/07/21 Javascript
解决vuecli3中img src 的引入问题
2020/08/04 Javascript
用Python设计一个经典小游戏
2017/05/15 Python
基于Django的ModelForm组件(详解)
2017/12/07 Python
python操作oracle的完整教程分享
2018/01/30 Python
python对excel文档去重及求和的实例
2018/04/18 Python
django多种支付、并发订单处理实例代码
2019/12/13 Python
移动端Web页面的CSS3 flex布局快速上手指南
2016/05/31 HTML / CSS
ProBikeKit美国官网:自行车套件,跑步和铁人三项套件
2016/10/13 全球购物
What's the difference between an interface and abstract class? (接口与抽象类有什么区别)
2012/10/29 面试题
Java程序员综合测试题
2014/04/25 面试题
学校运动会广播稿100条
2014/09/14 职场文书
2014最新自愿离婚协议书范本
2014/11/19 职场文书
2015年宣传思想工作总结
2015/05/22 职场文书
《群青的幻想曲》京力秋树角色PV公开
2022/04/08 日漫
linux目录管理方法介绍
2022/06/01 Servers