一、前言                          

在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. 高性能网站架构设计之缓存篇(4)- Redis 主从复制

    Redis 的主从复制配置非常容易,但我们先来了解一下它的一些特性. redis 使用异步复制.从 redis 2.8 开始,slave 也会周期性的告诉 master 现在的数据量.可能只是个机制, ...

  2. 【Bugly干货分享】iOS内存管理:从MRC到ARC实践

    Bugly 技术干货系列内容主要涉及移动开发方向,是由Bugly邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 对于iOS程序员来说,内存管理是入门的 ...

  3. PostgreSQL基础整理(一)

    1. 创建数据库: 1)登录bin目录,createdb.exe -U postgres -e mydb; -U 表示本次操作的登录用户名,如果不写会取windows登录的账户,如Administra ...

  4. 原创教程:《metasploit新手指南》介绍及下载

    原创教程:<metasploit新手指南>介绍及下载 1.1 作者简介 这份教程并不是“玄魂工作室”原创,但是我还是要力推给大家.相比那些一连几年都在问“我怎么才能入门”的人而言,我们更欣 ...

  5. Matrix Admin 后台模板笔记

    一个后台模板用久了就想换一个.上次找到了Matrix Admin.和ACE一样都是Bootstrap风格,比较容易上手.Matrix要更健壮些.感觉拿去做用户界面也是可以的. 整体风格: 1.表单验证 ...

  6. 全面理解Javascript闭包和闭包的几种写法及用途

    好久没有写博客了,过了一个十一长假都变懒了,今天总算是恢复状态了.好了,进入正题,今天来说一说javascript里面的闭包吧!本篇博客主要讲一些实用的东西,主要将闭包的写法.用法和用途.  一.什么 ...

  7. GOOGLE PROTOBUF开发者指南

    原文地址:http://www.cppblog.com/liquidx/archive/2009/06/23/88366.html 译者: gashero 目录 1   概览 1.1   什么是pro ...

  8. 一则JVM memory leak解决的过程

    起因是我们的集群应用(3台机器)新版本测试过程中,一般的JVM内存占用 都在1G左右, 但在运行了一段时间后,慢慢升到了4G, 这是一个明显不正常的现象. 定位 过程: 1.先在该机器上按照步骤尝试重 ...

  9. SpringMVC自定义注入controller变量

    springmvc config the controller parameter injection 问题描述 在SpringMVC中默认可以注入Model,ModelAndView,@Reques ...

  10. Java基础之如何解决斗地主问题

        难的是逻辑的分析,把逻辑转化成代码是一种能力,这种能力需要多练习总结.     多多指教,共同进步. 问题: 要求实现斗地主游戏发牌过程,打印三个玩家的牌和底牌.在不看底牌的情况下,统计出三个 ...