javascript 如何找到两个或多个节点的最近共同祖先?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3960843/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to find the nearest common ancestors of two or more nodes?
提问by Ozgur
Users selects two or more elements in a HTML page. What i want to accomplish is to find those elements' common ancestors (so body node would be the common ancestor if none found before) ?
用户在 HTML 页面中选择两个或多个元素。我想要完成的是找到这些元素的共同祖先(如果之前没有找到,那么身体节点将是共同祖先)?
P.S: It can be achieved with XPath but it is not a preferable option for me. Also it may be found with css selector parsing but i think it is a dirty method (?)
PS:它可以用 XPath 实现,但对我来说不是一个更好的选择。也可以通过 css 选择器解析找到它,但我认为这是一种肮脏的方法(?)
Thank you.
谢谢你。
采纳答案by Bobby Hyman
Try this:
试试这个:
function get_common_ancestor(a, b)
{
$parentsa = $(a).parents();
$parentsb = $(b).parents();
var found = null;
$parentsa.each(function() {
var thisa = this;
$parentsb.each(function() {
if (thisa == this)
{
found = this;
return false;
}
});
if (found) return false;
});
return found;
}
Use it like this:
像这样使用它:
var el = get_common_ancestor("#id_of_one_element", "#id_of_another_element");
That's just rattled out pretty quickly, but it should work. Should be easy to amend if you want something slightly different (e.g. jQuery object returned instead of DOM element, DOM elements as arguments rather than IDs, etc.)
这只是很快就结束了,但它应该有效。如果您想要稍微不同的东西,应该很容易修改(例如,返回 jQuery 对象而不是 DOM 元素,将 DOM 元素作为参数而不是 ID 等)
回答by benpickles
Here's a pure JavaScript version that is a little more efficient.
这是一个更高效的纯 JavaScript 版本。
function parents(node) {
var nodes = [node]
for (; node; node = node.parentNode) {
nodes.unshift(node)
}
return nodes
}
function commonAncestor(node1, node2) {
var parents1 = parents(node1)
var parents2 = parents(node2)
if (parents1[0] != parents2[0]) throw "No common ancestor!"
for (var i = 0; i < parents1.length; i++) {
if (parents1[i] != parents2[i]) return parents1[i - 1]
}
}
回答by lonesomeday
The solutions involving manually going through the ancestor elements are far more complicated than necessary. You don't need to do the loops manually. Get all the ancestor elements of one element with parents(), reduce it to the ones that contain the second element with has(), then get the first ancestor with first().
涉及手动遍历祖先元素的解决方案远比必要的复杂。您不需要手动执行循环。获取一个元素 with 的所有祖先元素parents(),将其缩减为包含第二个元素 with 的元素has(),然后获取第一个祖先 with first()。
var a = $('#a'),
b = $('#b'),
closestCommonAncestor = a.parents().has(b).first();
回答by Andy E
Here's another puremethod that uses element.compareDocumentPosition()and element.contains(), the former being a standards method and the latter being a method supported by most major browsers excluding Firefox:
这是另一种使用and 的纯方法,前者是标准方法,后者是大多数主要浏览器(Firefox 除外)支持的方法:element.compareDocumentPosition()element.contains()
Comparing two nodes
比较两个节点
function getCommonAncestor(node1, node2) {
var method = "contains" in node1 ? "contains" : "compareDocumentPosition",
test = method === "contains" ? 1 : 0x10;
while (node1 = node1.parentNode) {
if ((node1[method](node2) & test) === test)
return node1;
}
return null;
}
Working demo: http://jsfiddle.net/3FaRr/(using lonesomeday's test case)
工作演示:http: //jsfiddle.net/3FaRr/(使用 lonesomeday 的测试用例)
This should be, more or less, as efficient as possible since it is pure DOM and has only one loop.
这应该或多或少地尽可能高效,因为它是纯 DOM 并且只有一个循环。
Comparing two or morenodes
比较两个或多个节点
Taking another look at the question, I noticed the "or more"part of the "two or more"requirement had gone ignored by the answers. So I decided to tweak mine slightly to allow any number of nodes to be specified:
再看一下这个问题,我注意到“两个或更多”要求的“或更多”部分被答案忽略了。所以我决定稍微调整我的以允许指定任意数量的节点:
function getCommonAncestor(node1 /*, node2, node3, ... nodeN */) {
if (arguments.length < 2)
throw new Error("getCommonAncestor: not enough parameters");
var i,
method = "contains" in node1 ? "contains" : "compareDocumentPosition",
test = method === "contains" ? 1 : 0x0010,
nodes = [].slice.call(arguments, 1);
rocking:
while (node1 = node1.parentNode) {
i = nodes.length;
while (i--) {
if ((node1[method](nodes[i]) & test) !== test)
continue rocking;
}
return node1;
}
return null;
}
Working demo: http://jsfiddle.net/AndyE/3FaRr/1
回答by user3326824
The commonAncestorContainerproperty of the he Range API mentioned above, alongside its selectNode, makes this a no-brainer.
上面提到的 Range API的commonAncestorContainer属性,连同它的selectNode,使这变得轻而易举。
Run ("display") this code in Firefox's Scratchpad or a similar editor:
在 Firefox 的 Scratchpad 或类似的编辑器中运行(“显示”)此代码:
var range = document.createRange();
var nodes = [document.head, document.body]; // or any other set of nodes
nodes.forEach(range.selectNode, range);
range.commonAncestorContainer;
Note that both APIs are not supported by IE 8 or below.
请注意,IE 8 或更低版本不支持这两个 API。
回答by Pointy
You should be able to use the jQuery .parents()function and then walk through the results looking for the first match. (Or I guess you could start from the end and go backwards until you see the first difference; that's probably better.)
您应该能够使用 jQuery.parents()函数,然后遍历结果以查找第一个匹配项。(或者我想你可以从结尾开始然后倒退直到你看到第一个差异;这可能更好。)
回答by Daniel Trebbien
You can also use a DOM Range(when supported by the browser, of course). If you create a Rangewith the startContainerset to the earlier node in the document and the endContainerset to the later node in the document, then the commonAncestorContainerattributeof such a Rangeis the deepest common ancestor node.
您也可以使用 DOM Range(当然,当浏览器支持时)。如果您创建一个Range与startContainer集到文档中较早节点和endContainer集到文档中后来的节点,然后在commonAncestorContainer属性这样的Range最深的共同祖先节点。
Here is some code implementing this idea:
下面是一些实现这个想法的代码:
function getCommonAncestor(node1, node2) {
var dp = node1.compareDocumentPosition(node2);
// If the bitmask includes the DOCUMENT_POSITION_DISCONNECTED bit, 0x1, or the
// DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC bit, 0x20, then the order is implementation
// specific.
if (dp & (0x1 | 0x20)) {
if (node1 === node2) return node1;
var node1AndAncestors = [node1];
while ((node1 = node1.parentNode) != null) {
node1AndAncestors.push(node1);
}
var node2AndAncestors = [node2];
while ((node2 = node2.parentNode) != null) {
node2AndAncestors.push(node2);
}
var len1 = node1AndAncestors.length;
var len2 = node2AndAncestors.length;
// If the last element of the two arrays is not the same, then `node1' and `node2' do
// not share a common ancestor.
if (node1AndAncestors[len1 - 1] !== node2AndAncestors[len2 - 1]) {
return null;
}
var i = 1;
for (;;) {
if (node1AndAncestors[len1 - 1 - i] !== node2AndAncestors[len2 - 1 - i]) {
// assert node1AndAncestors[len1 - 1 - i - 1] === node2AndAncestors[len2 - 1 - i - 1];
return node1AndAncestors[len1 - 1 - i - 1];
}
++i;
}
// assert false;
throw "Shouldn't reach here!";
}
// "If the two nodes being compared are the same node, then no flags are set on the return."
// http://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition
if (dp == 0) {
// assert node1 === node2;
return node1;
} else if (dp & 0x8) {
// assert node2.contains(node1);
return node2;
} else if (dp & 0x10) {
// assert node1.contains(node2);
return node1;
}
// In this case, `node2' precedes `node1'. Swap `node1' and `node2' so that `node1' precedes
// `node2'.
if (dp & 0x2) {
var tmp = node1;
node1 = node2;
node2 = tmp;
} else {
// assert dp & 0x4;
}
var range = node1.ownerDocument.createRange();
range.setStart(node1, 0);
range.setEnd(node2, 0);
return range.commonAncestorContainer;
}
回答by Tom Hubbard
This is a generalized take on lonesomeday's answer. Instead of only two elements it will take a full JQuery object.
这是对 lonesomeday 答案的概括。它将需要一个完整的 JQuery 对象,而不是只有两个元素。
function CommonAncestor(jq) {
var prnt = $(jq[0]);
jq.each(function () {
prnt = prnt.parents().add(prnt).has(this).last();
});
return prnt;
}
回答by Rycochet
Somewhat late to the party, but here's an elegant jQuery solution (since the question is tagged jQuery) -
聚会有点晚了,但这里有一个优雅的 jQuery 解决方案(因为问题被标记为 jQuery)-
/**
* Get all parents of an element including itself
* @returns {jQuerySelector}
*/
$.fn.family = function() {
var i, el, nodes = $();
for (i = 0; i < this.length; i++) {
for (el = this[i]; el !== document; el = el.parentNode) {
nodes.push(el);
}
}
return nodes;
};
/**
* Find the common ancestors in or against a selector
* @param selector {?(String|jQuerySelector|Element)}
* @returns {jQuerySelector}
*/
$.fn.common = function(selector) {
if (selector && this.is(selector)) {
return this;
}
var i,
$parents = (selector ? this : this.eq(0)).family(),
$targets = selector ? $(selector) : this.slice(1);
for (i = 0; i < $targets.length; i++) {
$parents = $parents.has($targets.eq(i).family());
}
return $parents;
};
/**
* Find the first common ancestor in or against a selector
* @param selector {?(String|jQuerySelector|Element)}
* @returns {jQuerySelector}
*/
$.fn.commonFirst = function(selector) {
return this.common(selector).first();
};
回答by AntonB
This doesn't require much code anymore to solve:
这不再需要太多代码来解决:
steps:
脚步:
- grab parent of node_a
- if parent of node_a contains node_b return parent (in our code, the parent is referenced as node_a)
- if parent does not contain node_b we need to keep going up the chain
- end return null
- 获取 node_a 的父节点
- 如果 node_a 的父节点包含 node_b 返回父节点(在我们的代码中,父节点被引用为 node_a)
- 如果 parent 不包含 node_b 我们需要继续向上链
- 结束返回空
code:
代码:
function getLowestCommonParent(node_a, node_b) {
while (node_a = node_a.parentElement) {
if (node_a.contains(node_b)) {
return node_a;
}
}
return null;
}

