vivos vienen una todos seres reino reciente que provenimos porque origen organismos mismo los dio definicion considera común comun biologia bacteria avance animales ancestro ancestral javascript jquery

javascript - vienen - ¿Cómo encontrar los ancestros comunes más cercanos de dos o más nodos?



reino que se considera el ancestro de los animales (12)

Aquí hay otro método puro que utiliza element.compareDocumentPosition() y element.contains() , el primero es un método estándar y el último es un método admitido por la mayoría de los navegadores principales, excepto Firefox:

Comparando dos nodos

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; }

Demostración de trabajo: http://jsfiddle.net/3FaRr/ (utilizando el caso de prueba de lonesomeday)

Esto debería ser, más o menos, lo más eficiente posible dado que es DOM puro y tiene solo un ciclo.

Comparando dos o más nodos

Echando otro vistazo a la pregunta, noté que la parte "o más" del requisito de "dos o más" había sido ignorada por las respuestas. Así que decidí ajustar el mío ligeramente para permitir que se especificaran varios nodos:

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; }

Demostración de trabajo: http://jsfiddle.net/AndyE/3FaRr/1

Los usuarios seleccionan dos o más elementos en una página HTML. Lo que quiero lograr es encontrar los ancestros comunes de esos elementos (¿entonces el nodo del cuerpo sería el ancestro común si no se hubiera encontrado antes)?

PD: Se puede lograr con XPath, pero no es una opción preferible para mí. También se puede encontrar con el análisis del selector CSS, pero creo que es un método sucio (?)

Gracias.


Aquí hay una manera más sucia de hacer esto. Es más fácil de entender, pero requiere modificación 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; }


Aquí hay una versión de JavaScript pura que es un poco más eficiente.

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] } }


Debería poder usar la función jQuery .parents() y luego recorrer los resultados buscando la primera coincidencia. (O supongo que podrías comenzar desde el final e ir hacia atrás hasta que veas la primera diferencia, eso es probablemente mejor).


Esta es una versión generalizada de la respuesta de un solo día. En lugar de solo dos elementos, necesitará un objeto JQuery completo.

function CommonAncestor(jq) { var prnt = $(jq[0]); jq.each(function () { prnt = prnt.parents().add(prnt).has(this).last(); }); return prnt; }


Esto ya no requiere mucho código para resolver:

pasos:

  1. tomar el padre de node_a
  2. si parent de node_a contiene node_b return parent (en nuestro código, se hace referencia al padre como node_a)
  3. si parent no contiene node_b, tenemos que seguir subiendo en la cadena
  4. end return null

código:

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


La propiedad commonAncestorContainer de la API de rango mencionada anteriormente, junto con su selectNode , lo hace obvio.

Ejecute ("muestre") este código en el Scratchpad de Firefox o un editor similar:

var range = document.createRange(); var nodes = [document.head, document.body]; // or any other set of nodes nodes.forEach(range.selectNode, range); range.commonAncestorContainer;

Tenga en cuenta que IE 8 no es compatible con ambas API.


Las soluciones que implican pasar manualmente a través de los elementos ancestros son mucho más complicadas de lo necesario. No necesita hacer los bucles manualmente. Obtenga todos los elementos ancestros de un elemento con los parents() , reduzca a los que contienen el segundo elemento con has() , luego obtenga el primer antecesor con first() .

var a = $(''#a''), b = $(''#b''), closestCommonAncestor = a.parents().has(b).first();

Ejemplo jsFiddle


Prueba esto:

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; }

Úselo así:

var el = get_common_ancestor("#id_of_one_element", "#id_of_another_element");

Eso se repitió bastante rápido, pero debería funcionar. Debe ser fácil de modificar si desea algo ligeramente diferente (por ejemplo, el objeto jQuery devuelto en lugar del elemento DOM, los elementos DOM como argumentos en lugar de los ID, etc.)


PureJS

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; }


También puede usar un Range DOM (cuando el navegador lo admita, por supuesto). Si crea un Range con startContainer configurado en el nodo anterior en el documento y el endContainer configurado en el nodo posterior en el documento, entonces el atributo commonAncestorContainer de dicho Range es el nodo ancestro común más profundo.

Aquí hay un código que implementa esta 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; }


Un poco tarde para la fiesta, pero aquí hay una elegante solución de jQuery (ya que la pregunta está etiquetada 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(); };