jQuery / Method / .parent() - 부모 요소 선택하는 메서드

jQuery의 .parent()는 선택한 각 요소에서 바로 위 부모 한 단계만 찾아 새 jQuery 객체로 반환합니다. .parent(".card")는 그 부모가 .card에 맞을 때만 결과에 넣으며, 조건에 맞는 조상을 찾으려고 더 올라가지 않습니다. 같은 부모를 공유하는 요소들을 선택하면 부모 결과에는 중복이 제거됩니다.

parent·parents·closest의 선택 범위

.parent() 공식 문서의 부모는 DOM(Document Object Model, 문서 객체 모델) 트리에서 바로 위에 있는 노드입니다. 화면에서 둘러싼 것처럼 보이는 상자나 CSS 배치 기준이 반드시 이 부모인 것은 아닙니다. $(".pick").parent().addClass("picked")처럼 결과에 jQuery 메서드를 연결할 수 있고, 원래 버튼을 옮기거나 삭제하지 않습니다.

메서드 탐색 범위 조건 사용 시 결과
.parent(".card") 바로 위 한 단계 바로 위 부모가 .card일 때만 포함
.parents(".card") 부모부터 위쪽 조상 조건에 맞는 조상들
.closest(".card") 자신부터 위쪽으로 각 출발 요소에서 가장 먼저 맞는 대상

여러 조상을 모두 얻으려면 .parents(), 중간 래퍼가 있어도 가장 가까운 카드를 찾으려면 .closest()가 맞습니다. .closest()는 자신도 검사하므로 카드 자체에서 호출하면 그 카드가 반환될 수 있습니다. .parent()의 인수는 부모를 거르는 선택자 문자열이며, 함수나 단계 수를 넣는 자리로 사용하지 않습니다.

중첩 카드에서 부모를 비교하는 전체 예제

아래 전체 코드를 UTF-8로 parent-demo.html에 저장하고 브라우저로 엽니다. 콘텐츠 전송망(CDN)의 공식 jQuery 파일 4.0.0을 읽으므로 인터넷 연결이 필요하며 별도 설치는 없습니다. 초기 선택, 래퍼 추가, 카드 추가, 초기화와 아래 콘솔 코드를 jQuery 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가 1개 나오며 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()를 다시 호출해 반영합니다.