如何找到两个或更多节点最近的共同祖先?

Ozg*_*gur 35 javascript jquery

用户在HTML页面中选择两个或多个元素.我想要完成的是找到那些元素的共同祖先(如果以前没有找到,那么身体节点将是共同的祖先)?

PS:它可以通过XPath实现,但对我来说它不是一个更好的选择.也可以通过css选择器解析找到它,但我认为它是一个脏方法(?)

谢谢.

ben*_*les 67

这是一个更高效的纯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]
  }
}
Run Code Online (Sandbox Code Playgroud)

  • +1不假设jQuery +一个非常优雅的解决方案. (11认同)
  • 如果DOM节点是兄弟节点,这将失败.您应该编辑代码来解决这个问题. (2认同)

lon*_*day 44

涉及手动遍历祖先元素的解决方案远比必要复杂得多.您不需要手动执行循环.获取一个元素的所有祖先元素parents(),将其减少为包含第二个元素的元素has(),然后获取第一个祖先first().

var a = $('#a'),
    b = $('#b'),
    closestCommonAncestor = a.parents().has(b).first();
Run Code Online (Sandbox Code Playgroud)

jsFiddle例子

  • +1比我的解决方案好得多,虽然你有一年时间考虑它;-) (2认同)

And*_*y E 9

这里还有一个纯粹的使用方法element.compareDocumentPosition()element.contains(),前者是一个标准的方法,而后者是通过排除火狐绝大多数主流浏览器所支持的方法:

比较两个节点

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;
}
Run Code Online (Sandbox Code Playgroud)

工作演示:http://jsfiddle.net/3FaRr/(使用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;
}
Run Code Online (Sandbox Code Playgroud)

工作演示:http://jsfiddle.net/AndyE/3FaRr/1

  • 整齐的!相同高性能代码的版本,也将其包装到“jQuery.fn.commonAncestor”插件中:http://jsfiddle.net/ecmanaut/Dv5Wj/ (2认同)

Bob*_*ack 8

试试这个:

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;
}
Run Code Online (Sandbox Code Playgroud)

像这样使用它:

var el = get_common_ancestor("#id_of_one_element", "#id_of_another_element");
Run Code Online (Sandbox Code Playgroud)

这只是很快就嘎嘎作响,但它应该有效.如果你想要稍微不同的东西(例如jQuery对象返回而不是DOM元素,DOM元素作为参数而不是ID等),应该很容易修改


小智 5

上面提到的Range API 的commonAncestorContainer属性,以及它的selectNode,使得这是一个明智的选择.

在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;
Run Code Online (Sandbox Code Playgroud)

请注意,IE 8或更低版本不支持这两种API.

  • 这不起作用,因为“selectNode”将范围的开始和结束设置为引用节点的父节点,请参阅http://jsfiddle.net/3FaRr/13/(应该提醒“thisOne”,但它找不到正确的共同祖先)。 (2认同)

Ant*_*onB 5

这不再需要太多代码来解决:

脚步:

  1. 获取node_a的父节点
  2. 如果node_a的父节点包含node_b,则返回父节点(在我们的代码中,父节点被引用为node_a)
  3. 如果父级不包含node_b,我们需要继续沿着链向上
  4. 结束返回空

代码:

function getLowestCommonParent(node_a, node_b) {
    while (node_a = node_a.parentElement) {
        if (node_a.contains(node_b)) {
            return node_a;
        }
    }

    return null;
}
Run Code Online (Sandbox Code Playgroud)