一、前言                          

在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. javascript的执行和预解析

    很久以前遇到过一个面试题目,的的确确是面试官问我的问题,下面是这个问题的代码部分.由于年少无知,没有回答上,被无情pass了. var u ='hello world'; ;(function(){ ...

  2. ENode 1.0 - 消息的重试机制的设计思路

    项目开源地址:https://github.com/tangxuehua/enode 上一篇文章,简单介绍了enode框架中消息队列的设计思路,本文介绍一下enode框架中关系消息的重试机制的设计思路 ...

  3. 渣渣小本求职复习之路每天一博客系列——Java基础(9)

    ———————————————————————今天不闲聊————————————————————————————— 第十一章:线程 第四节:synchronized与同步 首先,我们来看一段代码: p ...

  4. [ZigBee] 14、Zigbee无线通信前奏——BasicRF 简单无线点对点传输协议

    本工程不带zigbee协议栈,作为过渡 实验现象: 一个EB2530(终端A)定义为发射模块,另一个EB2530(终端B)定义为接收模块. 按EB2530(终端A)上的按键,无线控制另一个EB2530 ...

  5. iOS——Core Animation 知识摘抄(四)

    原文地址http://www.cocoachina.com/ios/20150106/10840.html 延迟解压 一旦图片文件被加载就必须要进行解码,解码过程是一个相当复杂的任务,需要消耗非常长的 ...

  6. 细说.NET 中的多线程 (一 概念)

    为什么使用多线程 使用户界面能够随时相应用户输入 当某个应用程序在进行大量运算时候,为了保证应用程序能够随时相应客户的输入,这个时候我们往往需要让大量运算和相应用户输入这两个行为在不同的线程中进行. ...

  7. php json与xml序列化/反序列化

    在web开发中对象的序列化与反序列化经常使用,比较主流的有json格式与xml格式的序列化与反序列化,今天想写个jsop的小demo,结果发现不会使用php序列化,查了一下资料,做个笔记 简单数组js ...

  8. 如何选择前端框架:ANGULAR VS EMBER VS REACT

    最近一段时间是令前端工程师们非常兴奋的时期,因为三大Web框架陆续发布新版本,让我们见识到了更强大的Web框架.Ember2.0在2个月之前已经发布,从1.0升级到2.0非常简单.几周之前React发 ...

  9. [HIMCM暑期班]第3课:一个博弈问题

    在一个街道平面图上,住着n个住户.有两个贩卖热狗的商贩,各自想要在街区里摆设一个小摊.每天住户都会去离他家50米范围内的最近的摊点消费.问: 1. 如果两位小贩摆设小摊的顺序有先后(设A先摆,然后B再 ...

  10. MyBatis学习总结(六)——调用存储过程

    一.提出需求 查询得到男性或女性的数量, 如果传入的是0就女性否则是男性 二.准备数据库表和存储过程 1 create table p_user( 2 id int primary key auto_ ...