Javascript 如何找到两个或多个节点最近的共同祖先?
用户在HTML页面中选择两个或多个元素。我想完成的是找到这些元素的共同祖先(因此,如果以前没有找到,那么body节点将是共同祖先) 附言:这可以通过XPath实现,但对我来说不是一个更好的选择。也可以通过css选择器解析找到它,但我认为它是一个肮脏的方法(?)Javascript 如何找到两个或多个节点最近的共同祖先?,javascript,jquery,Javascript,Jquery,用户在HTML页面中选择两个或多个元素。我想完成的是找到这些元素的共同祖先(因此,如果以前没有找到,那么body节点将是共同祖先) 附言:这可以通过XPath实现,但对我来说不是一个更好的选择。也可以通过css选择器解析找到它,但我认为它是一个肮脏的方法(?) 谢谢。您应该能够使用jQuery.parents()函数,然后遍历结果以查找第一个匹配项。(或者我想你可以从头开始,然后向后走,直到你看到第一个区别;这可能更好。)试试这个: function get_common_ancestor(a,
谢谢。您应该能够使用jQuery
.parents()
函数,然后遍历结果以查找第一个匹配项。(或者我想你可以从头开始,然后向后走,直到你看到第一个区别;这可能更好。)试试这个:
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;
}
像这样使用它:
var el = get_common_ancestor("#id_of_one_element", "#id_of_another_element");
这很快就解决了,但它应该会起作用。如果您想要一些稍微不同的东西(例如,返回jQuery对象而不是DOM元素,将DOM元素作为参数而不是ID,等等),那么应该很容易修改。这里有一个纯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]
}
}
函数父节点(节点){
变量节点=[node]
for(;node;node=node.parentNode){
节点。取消移位(节点)
}
返回节点
}
功能公共祖先(节点1、节点2){
var parents1=父节点(节点1)
var parents2=父节点(node2)
如果(parents1[0]!=parents2[0])抛出“无共同祖先!”
对于(变量i=0;i
涉及手动遍历祖先元素的解决方案远比必要的复杂。您不需要手动执行循环。使用获取一个元素的所有祖先元素,将其缩减为包含第二个元素的元素,然后使用获取第一个祖先
这里是另一个纯方法,它使用元素。compareDocumentPosition()
和元素。contains()
,前者是标准方法,后者是除Firefox之外的大多数主要浏览器支持的方法:
比较两个节点
工作演示:(使用lonesomeday的测试用例)
这应该或多或少地尽可能有效,因为它是纯DOM并且只有一个循环
比较两个或多个节点 再看看这个问题,我注意到“两个或更多”要求中的“或更多”部分被答案忽略了。因此,我决定稍微调整我的节点,以允许指定任意数量的节点:
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;
}
函数getCommonSenator(node1/*、node2、node3、…nodeN*/){
if(arguments.length<2)
抛出新错误(“GetCommonSenator:参数不足”);
var i,
method=“contains”在节点1中“contains”:“compareDocumentPosition”,
测试=方法==“包含”?1:0x0010,
nodes=[].slice.call(参数,1);
摇摆:
while(node1=node1.parentNode){
i=节点长度;
而(我--){
if((节点1[方法](节点[i])&测试)!==测试)
继续摇摆;
}
返回节点1;
}
返回null;
}
工作演示:
您还可以使用DOM(当然,当浏览器支持时)。如果创建一个范围
,将startContainer
设置为文档中较早的节点,将endContainer
设置为文档中较后的节点,则该范围
的最深公共祖先节点
下面是一些实现此想法的代码:
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;
}
这是对lonesomeday答案的概括理解。它将采用一个完整的JQuery对象,而不仅仅是两个元素
function CommonAncestor(jq) {
var prnt = $(jq[0]);
jq.each(function () {
prnt = prnt.parents().add(prnt).has(this).last();
});
return prnt;
}
有点晚了,但这里有一个优雅的jQuery解决方案(因为问题被标记为jQuery)-
/**
*获取一个元素的所有父元素,包括其自身
*@returns{jQuerySelector}
*/
$.fn.family=函数(){
变量i,el,节点=$();
对于(i=0;i
上面提到的he Range API的属性,以及它的特性,使得这一点非常简单
在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;
请注意,IE8或更低版本不支持这两种API。这里是一种更脏的方法。更容易理解,但需要修改dom:
function commonAncestor(node1,node2){
var tmp1 = node1,tmp2 = node2;
// find node1's first parent whose nodeType == 1
while(tmp1.nodeType != 1){
tmp1 = tmp1.parentNode;
}
// insert an invisible span contains a strange character that no one
// would use
// if you need to use this function many times,create the span outside
// so you can use it without creating every time
var span = document.createElement('span')
, strange_char = '\uee99';
span.style.display='none';
span.innerHTML = strange_char;
tmp1.appendChild(span);
// find node2's first parent which contains that odd character, that
// would be the node we are looking for
while(tmp2.innerHTML.indexOf(strange_char) == -1){
tmp2 = tmp2.parentNode;
}
// remove that dirty span
tmp1.removeChild(span);
return tmp2;
}
这不再需要太多代码来解决: 步骤:
不喜欢上面的任何答案(想要纯javascript和一个函数)。 这对我来说非常有效,也更容易理解:
const findCommonAncestor = (elem, elem2) => {
let parent1 = elem.parentElement,parent2 = elem2.parentElement;
let childrensOfParent1 = [],childrensOfParent2 = [];
while (parent1 !== null && parent2 !== null) {
if (parent1 !== !null) {
childrensOfParent2.push(parent2);
if (childrensOfParent2.includes(parent1)) return parent1;
}
if (parent2 !== !null) {
childrensOfParent1.push(parent1);
if (childrensOfParent1.includes(parent2)) return parent2;
}
parent1 = parent1.parentElement;
parent2 = parent1.parentElement;
}
return null;
};
这是一个JavaScript ES6版本,它使用和,可以使用任意数量的元素作为参数:
function closestCommonAncestor(...elements) {
const reducer = (prev, current) => current.parentElement.contains(prev) ? current.parentElement : prev;
return elements.reduce(reducer, elements[0]);
}
const element1 = document.getElementById('element1');
const element2 = document.getElementById('element2');
const commonAncestor = closestCommonAncestor(element1, element2);
根据Andy E和AntonB 处理边缘情况:
node1==node2
和node1.contains(node2)
函数getCommonParentNode(节点1,
function commonAncestor(node1,node2){
var tmp1 = node1,tmp2 = node2;
// find node1's first parent whose nodeType == 1
while(tmp1.nodeType != 1){
tmp1 = tmp1.parentNode;
}
// insert an invisible span contains a strange character that no one
// would use
// if you need to use this function many times,create the span outside
// so you can use it without creating every time
var span = document.createElement('span')
, strange_char = '\uee99';
span.style.display='none';
span.innerHTML = strange_char;
tmp1.appendChild(span);
// find node2's first parent which contains that odd character, that
// would be the node we are looking for
while(tmp2.innerHTML.indexOf(strange_char) == -1){
tmp2 = tmp2.parentNode;
}
// remove that dirty span
tmp1.removeChild(span);
return tmp2;
}
function getLowestCommonParent(node_a, node_b) {
while (node_a = node_a.parentElement) {
if (node_a.contains(node_b)) {
return node_a;
}
}
return null;
}
function getFirstCommonAncestor(nodeA, nodeB) {
const parentsOfA = this.getParents(nodeA);
const parentsOfB = this.getParents(nodeB);
return parentsOfA.find((item) => parentsOfB.indexOf(item) !== -1);
}
function getParents(node) {
const result = [];
while (node = node.parentElement) {
result.push(node);
}
return result;
}
const findCommonAncestor = (elem, elem2) => {
let parent1 = elem.parentElement,parent2 = elem2.parentElement;
let childrensOfParent1 = [],childrensOfParent2 = [];
while (parent1 !== null && parent2 !== null) {
if (parent1 !== !null) {
childrensOfParent2.push(parent2);
if (childrensOfParent2.includes(parent1)) return parent1;
}
if (parent2 !== !null) {
childrensOfParent1.push(parent1);
if (childrensOfParent1.includes(parent2)) return parent2;
}
parent1 = parent1.parentElement;
parent2 = parent1.parentElement;
}
return null;
};
function closestCommonAncestor(...elements) {
const reducer = (prev, current) => current.parentElement.contains(prev) ? current.parentElement : prev;
return elements.reduce(reducer, elements[0]);
}
const element1 = document.getElementById('element1');
const element2 = document.getElementById('element2');
const commonAncestor = closestCommonAncestor(element1, element2);