jQuery / メソッド / .parent() - 直接の親要素を選択する

jQueryの.parent()は選択した各要素から一段だけ上の直接の親を取得し、新しいjQueryオブジェクトとして返します。.parent(".card")はその親が.cardに一致するときだけ結果に含め、一致する祖先を求めてさらに上へは進みません。複数要素が共有する親の重複は除かれます。

parent・parents・closestの範囲

.parent()の仕様でいう親はDOM(Document Object Model、文書オブジェクトモデル)ツリーで直接上にあるノードです。見た目を囲む箱やCSSの配置基準と同じとは限りません。$(".pick").parent().addClass("picked")のように結果へメソッドをつなげられます。元のボタンは移動・削除されません。

メソッド 範囲 選択子を使った結果
.parent(".card") 直接上の一段 直接の親が.cardなら含む
.parents(".card") 親から上の祖先 一致する祖先
.closest(".card") 自身から上へ 各出発要素から最初に一致した対象

一致する祖先をすべて得るなら.parents()、途中にラッパーがあっても最も近いカードを得るなら.closest()を使います。closestは自身も調べるため、カードで呼ぶとそのカードが返る場合があります。parentの引数は絞り込み用の選択子文字列であり、関数や段数を渡す場所ではありません。

入れ子のカードで比較する完全な例

全コードをUTF-8のparent-demo.htmlに保存し、ブラウザーで開きます。コンテンツ配信ネットワーク(CDN)の公式jQueryファイル4.0.0を読むインターネット接続が必要ですが、インストールは不要です。初期選択、ラッパー追加、カード追加、初期化と以下のConsole例を3.7.1・4.0.0で実行しました。

Alphaのボタン二つの直接の親は共通のalpha-bodyです。Betaのボタンはカードbetaの直下にあります。青緑の枠が探索結果です。buttonsは出発ボタン数、parentsは結果数、DOM buttonsは文書に残る実際のボタン数です。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Select immediate parents</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    body { font: 16px/1.6 sans-serif; max-width: 760px; margin: 24px auto; padding: 16px; }
    button, select { font: inherit; margin: 4px; max-width: 100%; }
    #board, .card, .body { padding: 12px; border: 1px solid #aab; margin: 10px 0; }
    .picked { outline: 3px solid teal; outline-offset: -3px; }
    pre { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <h1>Select one parent level</h1>
  <label for="mode">Traversal:</label>
  <select id="mode">
    <option value="parent">parent()</option>
    <option value="filtered">parent('.card')</option>
    <option value="closest">closest('.card')</option>
    <option value="ancestors">parents('section')</option>
  </select>
  <button type="button" id="wrap">Wrap Beta button</button>
  <button type="button" id="add">Add Gamma</button>
  <button type="button" id="reset">Reset</button>
  <section id="board">
    <article id="alpha" class="card">
      Alpha card
      <div id="alpha-body" class="body">
        <button type="button" id="alpha-one" class="pick"><span>Alpha one</span></button>
        <button type="button" id="alpha-two" class="pick">Alpha two</button>
      </div>
    </article>
    <article id="beta" class="card">
      Beta card
      <button type="button" id="beta-one" class="pick">Beta one</button>
    </article>
  </section>
  <pre id="result" aria-live="polite"></pre>
  <script>
    $(function () {
      const initial = $("#board").html();
      function report() {
        const $buttons = $("#board .pick");
        let $result;
        switch ($("#mode").val()) {
          case "filtered": $result = $buttons.parent(".card"); break;
          case "closest": $result = $buttons.closest(".card"); break;
          case "ancestors": $result = $buttons.parents("section"); break;
          default: $result = $buttons.parent();
        }
        $("#board, #board *").removeClass("picked");
        $result.addClass("picked");
        const ids = $result.map(function () { return this.id; }).get();
        $("#result").text(`buttons=${$buttons.length}\nparents=${$result.length}: ${ids.join(", ") || "(none)"}\nDOM buttons=${$("#board .pick").length}`);
      }
      $("#mode").on("change", report);
      $("#wrap").on("click", function () {
        if (!$("#beta-body").length) $("#beta-one").wrap('<div id="beta-body" class="body"></div>');
        report();
      });
      $("#add").on("click", function () {
        if (!$("#gamma").length) $("#board").append('<article id="gamma" class="card">Gamma card <button type="button" id="gamma-one" class="pick">Gamma one</button></article>');
        report();
      });
      $("#reset").on("click", function () { $("#board").html(initial); $("#mode").val("parent"); report(); });
      report();
    });
  </script>
</body>
</html>

初期状態はボタン3個に対し、共通親が一つにまとまるので親は2個です。

buttons=3
parents=2: alpha-body, beta
DOM buttons=3
  1. parent('.card')ではbetaだけです。Alphaの直接の親は.bodyなので除外され、その上のalphaには進みません。
  2. closest('.card')ではalpha・betaです。Alphaの中間.bodyを越えて最も近いカードを見つけます。
  3. parents('section')では共通祖先boardが一度だけ現れます。
  4. Wrap Beta buttonを押してparent()に戻るとalpha-body・beta-bodyになります。parent('.card')は0個です。ボタンは残り、直接上の構造だけが変わっています。
  5. Add Gammaは新しいカード直下にボタンを追加します。parent('.card')はgamma一つ、closest('.card')はカード3個です。繰り返しのラップ・追加は重複を防ぎます。
  6. Resetで元のHTMLとparent()モードに戻ります。条件を一つずつ変更して初期化すると比較しやすくなります。

report()は毎回ボタンを選び直します。const $parents = $(".pick").parent()として保持した結果が、後のDOM構造変更を自動反映するわけではありません。ラッパー追加や移動後、現在の関係が必要な時点で再探索します。

重複・空の結果とイベントの出発点

Reset後、開発者ツールのConsoleで次を実行します。空の選択のparentも長さ0のjQueryオブジェクトです。ノードを直接使う前に.lengthを確認します。htmlの親は通常要素でなくdocumentになる場合があり、すべての結果にtagNameなど要素専用プロパティがあると仮定しません。

const $buttons = $("#board .pick");
console.log(`buttons=${$buttons.length}; unique parents=${$buttons.parent().length}`);
console.log(`Alpha parent(.card)=${$("#alpha-one").parent(".card").length}`);
console.log(`Alpha closest(.card)=${$("#alpha-one").closest(".card").attr("id")}`);
console.log(`card closest itself=${$("#alpha").closest(".card").attr("id")}`);
console.log(`empty parent count=${$().parent().length}`);
console.log(`html parent is document=${$("html").parent().get(0) === document}`);
buttons=3; unique parents=2
Alpha parent(.card)=0
Alpha closest(.card)=alpha
card closest itself=alpha
empty parent count=0
html parent is document=true

ボタン内のspanをクリックするとevent.targetはspanです。一致したボタンを基準にするには、委譲イベントの通常関数内でthisを使います。同じページのConsoleで次を実行するとspanのクリックを発生させ、最後に自分の名前空間の処理だけを解除します。

$("#board").on("click.parentExample", ".pick", function (event) {
  console.log(`parent=${$(this).parent().attr("id")}; target=${event.target.tagName.toLowerCase()}`);
});
$("#alpha-one span").trigger("click");
$("#board").off(".parentExample");
parent=alpha-body; target=span

対象がspanでも親はボタンのalpha-bodyです。$(event.target).parent()ならボタン自体になります。この使い方でアロー関数のthisはボタンを指さないため通常関数を使います。カードが目的なら、そのボタンで.closest(".card")を呼びます。

予想外の親が選ばれたとき

開発者ツールのElementsで実DOMを確認します。プラグインが中間divを追加すると.parent(".card")が空になる場合があります。.parent().parent()を無作為に増やす前に、直接の親が必要か最も近い一致祖先が必要かを決めます。

出発選択子が広いと無関係な親にも一括でスタイルが付きます。#board .pickのように範囲を絞り、結果の.lengthとidを見てから変更します。結果が空でもすぐ作成せず、実行時点で出発要素が存在するか確認します。例のDOM準備関数は初期HTMLの解析後に実行し、後から追加したボタンはreport()の再呼び出しで反映します。