jQuery / Selector / :has() - Seleccionar elementos que contienen una coincidencia

En jQuery, :has(selector) selecciona los elementos que contienen al menos un descendiente coincidente. $("#menu > li:has(a)") devuelve los li que contienen enlaces, no los a interiores. Los descendientes incluyen tanto hijos directos como elementos más profundos; distingue la relación cuando solo deban contar los hijos inmediatos.

El contenedor frente al elemento contenido

La documentación de :has() explica que conserva candidatos con algún descendiente coincidente. Un enlace dentro de un span sigue siendo descendiente del li y cumple li:has(a). La clase has-link por sí sola no cumple la condición: debe existir un elemento a real dentro del li.

Expresión Condición Elementos devueltos
li:has(a) Un a a cualquier profundidad Los li exteriores coincidentes
li:has(> a) Un a como hijo directo Los li exteriores coincidentes
Seleccionar li y aplicar .has("a") Un descendiente a dentro del li Un subconjunto de los li seleccionados
Seleccionar li y aplicar .find("a") Buscar a dentro de los li Los a interiores

> expresa la relación de hijo directo. El #menu > li exterior determina los candidatos; > a dentro de :has() define la relación que se comprueba en cada uno. Separar ambos papeles ayuda a evitar coincidencias inesperadas en estructuras anidadas.

Ejemplo completo para resaltar elementos con enlaces

Guarda todo el código como UTF-8 en has-demo.html y ábrelo en un navegador. No hace falta instalar paquetes, pero sí una conexión a internet para cargar la distribución oficial de jQuery desde su red de distribución de contenidos, o CDN. El ejemplo usa 4.0.0; se ejecutó y comprobó el comportamiento tanto en 3.7.1 como en 4.0.0.

plain no tiene enlace; direct tiene un a como hijo inmediato; nested contiene un a dentro de span; self solo tiene la clase has-link. Los botones permiten comparar condiciones y cambiar el documento.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Items that contain links</title>
  <style>
    body { font: 16px/1.6 sans-serif; margin: 24px; }
    #menu { list-style: none; padding: 0; max-width: 600px; }
    #menu > li { border: 2px solid #ddd; margin: 8px 0; padding: 12px; }
    #menu > li.marked { border-color: #c45f00; background: #fff5e7; }
    button { margin: 4px; padding: 8px; }
  </style>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
</head>
<body>
  <h1>Items that contain links</h1>
  <ul id="menu">
    <li id="plain">No link</li>
    <li id="direct"><a id="link-1" href="#example">Direct link</a></li>
    <li id="nested"><span><a id="link-2" href="#example">Nested link</a></span></li>
    <li id="self" class="has-link">Class name only</li>
  </ul>
  <button type="button" id="descendants">Any descendant link</button>
  <button type="button" id="children">Direct child link</button>
  <button type="button" id="add">Add a link to plain</button>
  <button type="button" id="reset">Reset</button>
  <p id="result" aria-live="polite"></p>
  <script>
    $(function () {
      function show($chosen) {
        $("#menu > li").removeClass("marked");
        $chosen.addClass("marked");
        const ids = $chosen.map((_, item) => item.id).get().join(", ");
        $("#result").text(`${ids || "none"} | ${$chosen.length}`);
      }
      function refresh() {
        show($("#menu > li:has(a)"));
      }
      $("#descendants").on("click", refresh);
      $("#children").on("click", () => show($("#menu > li:has(> a)")));
      $("#add").on("click", function () {
        if (!$("#added-link").length) {
          $("#plain").append('<a id="added-link" href="#example"> Added link</a>');
        }
        refresh();
      });
      $("#reset").on("click", function () {
        $("#added-link").remove();
        refresh();
      });
      refresh();
    });
  </script>
</body>
</html>

Al abrir el archivo se resaltan los bordes exteriores de direct y nested y se muestra:

direct, nested | 2

Comparar botones y restablecer el ejemplo

  1. Any descendant link admite enlaces a cualquier profundidad: direct, nested | 2.
  2. Direct child link exige un a inmediatamente dentro del li: direct | 1. En nested hay un span intermedio.
  3. Add a link to plain añade un enlace y muestra plain, direct, nested | 3. La comprobación del identificador evita duplicarlo al pulsar otra vez.
  4. Reset elimina solo el enlace añadido y reaplica la condición de descendientes. El texto No link y los elementos originales se conservan; vuelven a coincidir dos entradas.

show quita primero las marcas anteriores y después aplica la selección nueva. refresh vuelve a consultar la estructura actual. Así se evita usar una selección antigua tras añadir contenido o conservar marcas incorrectas al cambiar de condición.

Comparar .has() con .find()

Si los li ya están seleccionados, el método .has() conserva los candidatos adecuados. Justo después de abrir el archivo o pulsar Reset, ejecuta este fragmento en la consola de las herramientas de desarrollo:

const ids = $set => $set.map((_, item) => item.id).get().join(", ");
console.log(ids($("#menu > li:has(a)")));
console.log(ids($("#menu > li").has("a")));
console.log(ids($("#menu > li").find("a")));
direct, nested
direct, nested
link-1, link-2

Las dos primeras líneas muestran los identificadores de los li exteriores; .find() devuelve los de los a interiores. .has() filtra una selección existente según sus descendientes, sin buscar padres desconocidos hacia arriba. .filter() comprueba los propios candidatos.

.has() admite una cadena selectora o un elemento concreto del modelo de objetos del documento, DOM. También con un elemento solo conserva candidatos que lo contienen como descendiente. El propio candidato no cuenta como descendiente de sí mismo; si no hay coincidencias internas, el resultado queda vacío.

Delimitar, volver a consultar y distinguir CSS

Una expresión sin delimitar como $(":has(a)") puede seleccionar varios antepasados que contienen enlaces. Especifica el contenedor y las etiquetas necesarias. La selección no se actualiza automáticamente: vuelve a ejecutarla tras añadir o eliminar descendientes.

Selectors Level 4 también define :has() en CSS. Por tanto, sería inexacto afirmar que esta sintaxis siempre es exclusiva de jQuery. Aquí se manipulan conjuntos de elementos mediante la API de jQuery. Para compararlo con reglas CSS de presentación, comprueba los navegadores admitidos y las condiciones selectoras necesarias.

Si algo falla, revisa la petición de jQuery en Network y los errores en Console. Comprueba paréntesis, comillas, etiquetas reales y anidación. Importa que exista un elemento a, no la palabra «enlace» en el texto ni un nombre de clase.