jQuery / Seletor / :has() - Selecionar elementos que contêm uma correspondência

No jQuery, :has(selector) seleciona elementos que contêm pelo menos um descendente correspondente. $("#menu > li:has(a)") retorna os li que contêm links, e não os a internos. Descendentes incluem filhos diretos e elementos em níveis mais profundos; especifique a relação quando apenas os filhos imediatos devem contar.

O contêiner e o elemento contido

A documentação de :has() descreve um filtro que mantém candidatos com algum descendente correspondente. Um link dentro de span ainda é descendente do li e satisfaz li:has(a). Somente a classe has-link não atende à condição: é necessário um elemento a real dentro do li.

Expressão Condição Elementos retornados
li:has(a) Um a em qualquer profundidade Os li externos correspondentes
li:has(> a) Um a como filho direto Os li externos correspondentes
Selecionar li e aplicar .has("a") Um descendente a dentro de cada li Um subconjunto dos li selecionados
Selecionar li e aplicar .find("a") Buscar a dentro dos li Os a internos

> indica a relação de filho direto. O trecho externo #menu > li define os candidatos; o > a dentro de :has() define a relação verificada em cada um. Distinguir essas funções evita correspondências inesperadas em estruturas aninhadas.

Exemplo completo para destacar itens com links

Salve todo o código em UTF-8 como has-demo.html e abra o arquivo em um navegador. Não é preciso instalar pacotes, mas é necessária conexão à internet para carregar a distribuição oficial do jQuery pela rede de distribuição de conteúdo, ou CDN. O exemplo usa 4.0.0; os comportamentos apresentados foram executados e verificados tanto em 3.7.1 quanto em 4.0.0.

plain não contém link; direct tem um a como filho imediato; nested contém a dentro de span; self possui apenas a classe has-link. Os botões comparam as condições e alteram o 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>

Inicialmente, as bordas externas de direct e nested ficam destacadas e o resultado mostra:

direct, nested | 2

Comparar os botões e restaurar o estado inicial

  1. Any descendant link aceita links em qualquer profundidade: direct, nested | 2.
  2. Direct child link exige um a imediatamente dentro do li: direct | 1. Em nested existe um span intermediário.
  3. Add a link to plain inclui um link e mostra plain, direct, nested | 3. A verificação do identificador impede duplicatas em cliques repetidos.
  4. Reset remove somente o link acrescentado e reaplica a condição de descendentes. O texto No link e os elementos originais permanecem; novamente dois itens correspondem.

show primeiro remove as marcações anteriores e depois aplica a seleção nova. refresh consulta a estrutura atual em cada chamada. Isso evita usar uma seleção antiga após a inclusão ou manter destaques incorretos ao trocar a condição.

Comparar .has() e .find()

Se os li já estiverem selecionados, o método .has() mantém os candidatos desejados. Logo após abrir o arquivo ou usar Reset, execute este trecho no console das ferramentas de desenvolvimento:

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

As duas primeiras linhas mostram os identificadores dos li externos; .find() retorna os dos a internos. .has() filtra a seleção existente por seus descendentes, sem subir para pais desconhecidos. .filter() testa os próprios candidatos.

.has() aceita uma string de seletor ou um elemento específico do modelo de objetos do documento, DOM. Mesmo com um elemento, mantém apenas candidatos que o contêm como descendente. O próprio candidato não conta como descendente de si mesmo; sem correspondência interna, o resultado é vazio.

Delimitar o escopo, consultar novamente e distinguir CSS

Uma expressão irrestrita como $(":has(a)") pode selecionar vários ancestrais contendo links. Especifique o contêiner e as tags necessárias. A seleção não é atualizada automaticamente: refaça a consulta depois de incluir ou remover descendentes.

Selectors Level 4 também define :has() em CSS. Assim, seria impreciso afirmar que essa sintaxe é sempre exclusiva do jQuery. Aqui, manipulamos conjuntos de elementos pela API do jQuery. Ao comparar com regras CSS de apresentação, verifique os navegadores atendidos e as condições de seleção necessárias.

Se houver falhas, examine a requisição do jQuery em Network e os erros em Console. Confira parênteses, aspas, tags reais e aninhamento. O que importa é existir um elemento a, não a palavra “link” no texto ou um nome de classe.