jQuery / Selector / :has() - 特定の要素を含む要素を選択するセレクター
jQueryの:has(selector)は、指定条件に一致する子孫を1つ以上含む要素を選びます。$("#menu > li:has(a)")が返すのはリンクを持つliで、内側のaではありません。子孫には直接の子とさらに深い要素が含まれるため、直下だけを調べたい場合は条件を分けます。
含む要素と含まれる要素の違い
:has()の仕様は、該当する子孫を持つ候補を残すことを説明しています。span内のリンクもliの子孫なのでli:has(a)に一致します。一方、liのクラス名がhas-linkというだけでは、実際のaを含む条件には一致しません。
| 式 | 調べる条件 | 返す対象 |
|---|---|---|
| li:has(a) | 内側のどこかにaがある | 条件に合うli |
| li:has(> a) | aが直接の子である | 条件に合うli |
| liを選び.has("a") | 選択済みliにaの子孫がある | 元のli集合の一部 |
| liを選び.find("a") | li内でaを探す | 内側のa |
>は直接の親子関係を示します。外側の#menu > liは候補の範囲、:has()内の> aは各候補の内部で調べる関係を決めます。この役割を分けて読むと、入れ子でも意図した条件を作れます。
リンクのある項目を強調する完全な例
全コードをUTF-8のhas-demo.htmlに保存してブラウザーで開きます。インストールは不要ですが、コンテンツ配信ネットワーク(CDN)から公式jQuery配布ファイルを読むため接続が必要です。例は4.0.0を使い、3.7.1と4.0.0で同じ動作を実行して確認しました。
plainにはリンクがなく、directは直下にa、nestedはspan内にaがあります。selfはhas-linkというクラス名だけです。ボタンで選択条件と文書変更を比較します。
<!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>
最初はdirectとnestedの外枠が強調され、次を表示します。
direct, nested | 2
直接の子・追加・初期化を確認する
- Any descendant linkは深さを問わずリンクを調べ、
direct, nested | 2です。 - Direct child linkは直下のaだけを調べ、
direct | 1です。nestedのリンクはspanの下なので対象外です。 - Add a link to plainでリンクを追加すると
plain, direct, nested | 3になります。再度押しても追加リンクのIDを確認して重複生成しません。 - Resetは追加したリンクだけを削除して標準の子孫条件を適用します。元のNo linkテキストや項目は残り、最初の2件に戻ります。
showは以前の強調を外して新しい結果にだけクラスを付けます。refreshは現在の文書を調べ直します。追加後に古い選択を使い続けることや、条件変更後も強調が残ることを防ぎます。
.has()と.find()を比較する
既にliを選んでいる場合は.has()メソッドで必要な候補を残せます。例を開いた直後かReset後に、開発者ツールのConsoleで次の部分コードを実行します。
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
最初の2つは外側のliのIDで、.find()は内側のaのIDです。.has()は親を探して上へ移動する処理ではなく、選択済みの集合を子孫条件で絞る処理です。候補自身に条件を適用する.filter()とも異なります。
.has()には選択文字列のほか、文書オブジェクトモデル(DOM)の特定要素も渡せます。その要素を子孫として含む候補を残します。候補自身だけが一致する場合や、一致する子孫がない場合は空の結果になります。
選択範囲・再選択・CSSとの関係
$(":has(a)")のように文書全体を候補にすると、リンクを含む複数の祖先まで選ぶ可能性があります。目的に応じてコンテナーとタグを限定します。選択結果は自動更新されないため、子孫の追加・削除後は選択し直して現在の構造を反映します。
Selectors Level 4にはCSSの:has()も定義されているため、常にjQuery専用と説明するのは正確ではありません。この記事はjQuery APIで要素集合を得る方法を扱います。CSS規則で見た目だけを変える方法と比較するときは、対象ブラウザーと必要な選択条件を確認します。
反応しない場合はNetworkでjQueryの読み込み、Consoleでエラーを確認します。括弧・引用符、実際のタグと入れ子の深さも調べます。表示上の「リンク」という文字やクラス名ではなく、a要素の構造が条件です。




