一、前言                          

在polyfill querySelectorAll 和写弹出窗时都需要判断两个节点间的位置关系,通过jQuery我们可以轻松搞定,但原生JS呢?下面我将整理各种判断方法,以供日后查阅。

二、祖孙关系                        

html

<div id="ancestor">
<div id="parent">
<div id="son">son</div>
</div>
</div>
<div id="other">other</div>

common.js

var ancestor = document.getElementById('ancestor');
var parent = document.getElementById('parent');
var son = document.getElementById('son');
var other = document.getElementById('other');

方法一:通过Selection对象

/** 定义判断祖孙关系函数
* @param {HTMLElement} parentNode
* @param {HTMLElement} sonNode
*/
var has = function(parentNode, sonNode){
   if (parentNode === sonNode) return true;

var selection = window.getSelection();
selection.selectAllChildren(parentNode);
var ret = selection.containsNode(sonNode, false); return ret;
}; // 调用
console.log(has(ancestor, son)); // 显示true
console.log(has(ancestor, other)); // 显示false

缺点:仅仅FF支持,其他浏览器一律无效

1. 执行 selection.selectAllChildren(parentNode) 时,parentNode的内容会被高亮,并且原来高亮的部分将被取消;

2. chrome下, selection.containsNode()恒返回false ;

3. IE9~11下的Selection类型对象没有containsNode方法;

4. IE5.5~8下没有Selection类型;

关于IE下的[object Selection]和[object MSSelection]类型(详细可浏览《JS魔法堂:细说Selection和MSSelection类型》)

1. IE11仅有[object Selection]类型

获取方式: document.getSelection() 或 window.getSelection()

2. IE9~10有[object MSSelection]和[object Selection]两种类型

获取[object MSSelection]: document.selection

获取[object Selection]: document.getSelection() 和 window.getSelection()

3. IE5.5~IE8仅有[object MSSelection]类型

获取方式: document.selection

注意:document.selection是IE的特有属性。

方法二:通过Range对象

var has = function(parentNode, sonNode){
if (parentNode === sonNode) return true;
var r1 = document.createRange(), r2 = document.createRange();
r1.selectNode(parentNode);
r2.selectNode(sonNode);
var startRet = r1.compareBoundaryPoints(Range.START_TO_START, r2);
var endRet = r1.compareBOundaryPoints(Range.END_TO_END, r2);
var ret = startRet === - && endRet === ; return ret;
};

缺点:不兼容IE5.5~8(IE9+、FF和Chrome均支持)

1. IE5.5~8没有 document.createRange() 方法

关于[object Range]、[object TextRange]和[object ControlRange]类型

  首先明确的是[object Range]是符合W3C标准的,而[object TextRange]和[object ControlRange]是IE独有的。

(详细可浏览《JS魔法堂:细说Range、TextRange和ControlRange类型》)

1. 通过document.createRange()创建[object Range]对象

2. 通过window.getSelection().getRangeAt({unsigned int32} index)获取[object Range]对象

3. 通过document.selection.createRange()或document.selection.createRangeCollection()方法获取[object TextRange]对象,并且无法像Range对象内容通过selectNode方法直接绑定到DOM片段中。

方法三:通过contains方法

var has = function(parentNode, sonNode){
return parentNode.contains(sonNode);
}; console.log(has(ancestor, ancestor));// 返回true
console.log(has(ancestor, son));// 返回true
console.log(has(ancestor, other));// 返回false

优点:简单直接

缺点:兼容性问题

支持——chrome、 firefox9+、 ie5+、 opera9.64+(估计从9.0+)、safari5.1.7+

不支持——FF

方法四:通过compareDocumentPosition方法

var has = function(parentNode, sonNode){
if (parentNode === sonNode) return true;
var rawRet = parentNode.compareDocumentPosition(sonNode);
var ret = !!(rawRet & 16); return ret;
};

compareDocumentPosition可以算是W3C标准中比较两节点位置关系的一大利器,不仅可以判断祖孙关系,还可以判断其他关系哦

var ret = A.compareDocumentPosition(B);

返回值ret的意思如下:

Bits          Number        Meaning 
000000         0              元素一致 
000001         1              节点在不同的文档(或者一个在文档之外) 
000010         2              节点 B 在节点 A 之前 
000100         4              节点 A 在节点 B 之前 
001000         8              节点 B 包含节点 A 
010000         16             节点 A 包含节点 B 
100000         32             浏览器的私有使用

方法五:递归遍历

var has = function(parentNode, sonNode){
if (parentNode === sonNode) return true;
var p = sonNode.parentNode;
if (!p.ownerDocument){
return false;
}
else if (p !== parentNode){
return has(parentNode, p);
}
else{
return true;
}
}

优点:所有浏览器均通用

缺点:当节点层级深时,效率较低。

综合方案一,来自司徒正美(http://m.cnblogs.com/57731/1583523.html?full=1):

//2013.1.24 by 司徒正美 
function contains(parentEl, el, container) {
// 第一个节点是否包含第二个节点
//contains 方法支持情况:chrome+ firefox9+ ie5+, opera9.64+(估计从9.0+),safari5.1.7+
if (parentEl == el) {
return true;
}
if (!el || !el.nodeType || el.nodeType != ) {
return false;
}
if (parentEl.contains ) {
return parentEl.contains(el);
}
if ( parentEl.compareDocumentPosition ) {
return !!(parentEl.compareDocumentPosition(el) & );
}
var prEl = el.parentNode;
while(prEl && prEl != container) {
if (prEl == parentEl)
return true;
prEl = prEl.parentNode;
}
return false;
}

综合方案二,来自Sizzle(https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L688)

注意:Sizzle的contains版本是contains(ancestor,ancestor)返回false的。

// Element contains another
// Purposefully does not implement inclusive descendent
// As in, an element does not contain itself
contains = hasCompare || rnative.test( docElem.contains ) ?
function( a, b ) {
var adown = a.nodeType === ? a.documentElement : a,
bup = b && b.parentNode;
return a === bup || !!( bup && bup.nodeType === && (
adown.contains ?
adown.contains( bup ) :
a.compareDocumentPosition && a.compareDocumentPosition( bup ) &
));
} :
function( a, b ) {
if ( b ) {
while ( (b = b.parentNode) ) {
if ( b === a ) {
return true;
}
}
}
return false;
};

综合方案三,我那又长又臭的版本^_^

var rNative = /[^{]+\{\s*\[native code\]\s*\}/;
var docEl = document.documentElement;
var contains = rNative.test(docEl.contains) && function(ancestor, descendant){
if (ancestor === descendant) return true; ancestor = ancestor.nodeType === ? ancestor.documentElement : ancestor;
return ancestor.contains(descendant);
} ||
rNative.test(docEl.compareDocumentPosition) &&
function(ancestor, descendant){
if (ancestor === descendant) return true; ancestor = ancestor.documentElement || ancestor;
return !!(ancestor.compareDocumentPosition(descendant) & );
} ||
rNative.test(document.createRange) &&
function(ancestor, descendant){
if (ancestor === descendant) return true; var r1 = document.createRange(), r2 = document.createRange();
r1.selectNode(ancestor.documentElement || ancestor);
r2.selectNode(descendant.documentElement || descendant);
  var startRet = r1.compareBoundaryPoints(Range.START_TO_START, r2);
var endRet = r1.compareBOundaryPoints(Range.END_TO_END, r2);
var ret = startRet === -1 && endRet === 1;
try{
r1.detach();
r2.detach();
}catch(e){} return ret;
} ||
function(ancestor, descendant){
if (ancestor === descendant) return true; var a = ancestor.documentElement || ancestor;
var b = (descendant.documentElement || descendant)['parentNode'];
while(!!b){
  if (a === b) return true;
b = b.parentNode;
}
return false;
};

 三、总结                              

尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/3931818.html^_^肥子John

JS魔法堂:判断节点位置关系的更多相关文章

  1. JS魔法堂:精确判断IE的文档模式by特征嗅探

    一.前言 苦逼的前端攻城狮都深受浏览器兼容之苦,再完成每一项功能前都要左顾右盼,生怕浏览器不支持某个API,生怕原生API内含臭虫因此判断浏览器类型和版本号成了不可绕过的一道关卡,而特征嗅探是继浏览器 ...

  2. JS魔法堂:浏览器模式和文档模式怎么玩?

    一.前言 从IE8开始引入了文档兼容模式的概念,作为开发人员的我们可以在开发人员工具中通过“浏览器模式”和“文档模式”(IE11开始改为“浏览器模式”改成更贴切的“用户代理字符串”)品味一番,它的出现 ...

  3. JS魔法堂:属性、特性,傻傻分不清楚

    一.前言 或许你和我一样都曾经被下面的代码所困扰 var el = document.getElementById('dummy'); el.hello = "test"; con ...

  4. JS魔法堂:追忆那些原始的选择器

    一.前言                                                                                                 ...

  5. JS魔法堂:LINK元素深入详解

    一.前言 我们一般使用方式为 <link type="text/css" rel="stylesheet" href="text.css&quo ...

  6. JS魔法堂:那些困扰你的DOM集合类型

    一.前言 大家先看看下面的js,猜猜结果会怎样吧! 可选答案: ①. 获取id属性值为id的节点元素 ②. 抛namedItem is undefined的异常 var nodes = documen ...

  7. JS魔法堂:不完全国际化&本地化手册 之 实战篇

    前言  最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求--国际化&本地化.熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"的阶段而已. ...

  8. JS魔法堂:IMG元素加载行为详解

    一.前言 在<JS魔法堂:jsDeferred源码剖析>中我们了解到img元素加载失败可以作为函数异步执行的优化方案,本文打算对img元素的加载行为进行更深入的探讨. 二.资源加载的相关属 ...

  9. JS魔法堂:jsDeferred源码剖析

    一.前言 最近在研究Promises/A+规范及实现,而Promise/A+规范的制定则很大程度地参考了由日本geek cho45发起的jsDeferred项目(<JavaScript框架设计& ...

随机推荐

  1. FFmpeg 2.1 发布

    FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.它包括了目前领先的音/视频编码库 libavcodec. FFmpeg是在Linux下开发出来的,但它可以在包括W ...

  2. 微信公共平台开发-(.net实现)3--发送文本消息

    最近,项目这边比较忙,没来得及续写,哎,先吐吐槽吧,在这个周六还得来上班,以后每个周六多要上,一天的滋味真有点受不鸟呀.还不习惯ing... 嗯,别的不说了现在开始接着上次http://www.cnb ...

  3. 人人都是 DBA(III)SQL Server 调度器

    在 SQL Server 中,当数据库启动后,SQL Server 会为每个物理 CPU(包括 Physical CPU 和 Hyperthreaded)创建一个对应的任务调度器(Scheduler) ...

  4. 用Python编写博客导出工具

    用Python编写博客导出工具 罗朝辉 (http://kesalin.github.io/) CC 许可,转载请注明出处   写在前面的话 我在 github 上用 octopress 搭建了个人博 ...

  5. Unity3D使用经验总结 编辑器扩展篇

    一个引擎,最重要的就是工具,工具除了提升开发速度,提供可视化操作环境以外,还带了容错功能. 它使得大家的工作局限在一定的范围内,比如一个变量的配置,或者是一些类型的选择. 使用编辑器,使得既使不太明白 ...

  6. OpenCV 3.0函数库索引

    ================================== 前言:值的个数: cvCrossProduct:计算两个三维向量的向量积(叉积): cvCvtColor:将数组的通道从一个颜色空 ...

  7. vmware安装ubuntu12.04嵌套安装xen server(实现嵌套虚拟化)

    环境准备 软件:vmware workstation 9.0    ubuntu-12.04.2-server-amd64(官方下载) 硬件:确认CPU支持虚拟化VM-T vmware设置 vmwar ...

  8. MVC5:使用Ajax和HTML5实现文件上传功能

    引言 在实际编程中,经常遇到实现文件上传并显示上传进度的功能,基于此目的,本文就为大家介绍不使用flash 或任何上传文件的插件来实现带有进度显示的文件上传功能. 基本功能:实现带有进度条的文件上传功 ...

  9. Move to Github

    Hi,各位,好久不见.我已经将博客移动到了 Github 上.原因是我喜欢用 Markdown 而不是 WYSIWYG 的编辑器写博客.请访问新地址: http://lxconan.github.io

  10. 关于session的小结

    session的原理 Session对象的原理在于,服务器可以为客户端创建并维护一个所谓的Session对象,用于存放数据. 在创建Session对象的同时,服务器将会为该Session对象产生一个唯 ...