jQuery / Selector / :odd - 홀수 인덱스 요소를 선택하는 선택자

jQuery의 :odd는 선택 결과를 0부터 세어 인덱스가 1, 3, 5인 요소, 즉 두 번째·네 번째·여섯 번째 요소를 고릅니다. ‘홀수 번째 줄’이라는 말만 보고 첫 번째 줄을 기대하면 결과가 반대로 보일 수 있습니다. 새 코드에서는 일반 CSS 선택자로 범위를 정한 뒤 .odd()로 거르는 방식을 사용합니다.

:odd의 뜻과 .odd()로 바꾸는 방법

:odd 공식 문서는 이 선택자를 jQuery 3.4부터 사용 중단 권고(deprecated) 대상으로 표시하고, .odd() 메서드를 대안으로 안내합니다. .odd()는 3.5에서 추가되었으며 인수를 받지 않고 현재 집합의 홀수 인덱스 요소로 새 jQuery 객체를 만듭니다. 사용 중단 권고와 제거는 다릅니다. 이 글의 :odd와 .odd() 비교는 3.7.1과 4.0.0에서 모두 실행해 일치함을 확인했습니다.

$("#records > tbody > tr:odd")는 $("#records > tbody > tr").odd()로 바꿀 수 있습니다. 기존 버전이 3.5보다 낮다면 .odd()가 없으므로 프로젝트와 플러그인의 호환성을 먼저 확인하고 업그레이드해야 합니다.

표현 세는 기준 초기 예제의 결과
tbody의 tr에 :odd 또는 .odd() 선택한 데이터 행 전체, 0부터 r2, r4
헤더를 포함한 모든 tr에 .odd() 헤더가 인덱스 0 r1, r3, r5
tbody의 tr:nth-child(odd) 부모 안의 형제 위치, 1부터 r1, r3, r5

헤더가 있는 표로 실행하기

다음 전체 코드를 UTF-8의 odd-demo.html로 저장하고 브라우저에서 엽니다. 별도의 패키지 설치는 필요하지 않지만 공식 배포 파일을 CDN(Content Delivery Network, 콘텐츠 전송 네트워크)에서 불러올 인터넷 연결이 필요합니다. 예제는 4.0.0을 사용합니다.

thead에 헤더 한 줄이 있고 tbody에는 데이터 다섯 줄이 있습니다. 행의 ID는 항목을 구별하는 이름이며, 실제 인덱스는 선택 범위와 현재 문서 순서로 결정됩니다. 버튼으로 범위와 조건을 바꾸어 봅니다.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Odd indexes in table rows</title>
  <style>
    body { font: 16px/1.6 sans-serif; margin: 24px; }
    table { border-collapse: collapse; width: 100%; max-width: 600px; }
    th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
    tr.marked { background: #fff0b3; }
    button { margin: 4px; padding: 8px; }
  </style>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
</head>
<body>
  <h1>Odd indexes in table rows</h1>
  <table id="records">
    <thead><tr id="heading"><th>Item</th><th>Amount</th></tr></thead>
    <tbody>
      <tr id="r1" class="eligible"><td>Item 1</td><td>10</td></tr>
      <tr id="r2"><td>Item 2</td><td>20</td></tr>
      <tr id="r3" class="eligible"><td>Item 3</td><td>30</td></tr>
      <tr id="r4" class="eligible"><td>Item 4</td><td>40</td></tr>
      <tr id="r5" class="eligible"><td>Item 5</td><td>50</td></tr>
    </tbody>
  </table>
  <button type="button" id="data">Data rows .odd()</button>
  <button type="button" id="all">Include header .odd()</button>
  <button type="button" id="css">Data :nth-child(odd)</button>
  <button type="button" id="prepend">Prepend one data row</button>
  <button type="button" id="reset">Reset</button>
  <p id="result" aria-live="polite"></p>
  <script>
    $(function () {
      function show($chosen) {
        $("#records tr").removeClass("marked");
        $chosen.addClass("marked");
        const ids = $chosen.map((_, row) => row.id).get().join(", ");
        $("#result").text(`${ids || "none"} | ${$chosen.length}`);
      }
      function dataRows() {
        show($("#records > tbody > tr").odd());
      }
      $("#data").on("click", dataRows);
      $("#all").on("click", () => show($("#records tr").odd()));
      $("#css").on("click", () => show($("#records > tbody > tr:nth-child(odd)")));
      $("#prepend").on("click", function () {
        if (!$("#extra").length) {
          $("#records > tbody").prepend('<tr id="extra"><td>Extra item</td><td>0</td></tr>');
        }
        dataRows();
      });
      $("#reset").on("click", function () {
        $("#extra").remove();
        dataRows();
      });
      dataRows();
    });
  </script>
</body>
</html>

처음에는 데이터 행만 선택한 뒤 .odd()를 적용하므로 Item 2와 Item 4가 강조되고 다음 결과가 나옵니다.

r2, r4 | 2

범위 변경과 행 추가 결과 확인하기

  1. Data rows .odd()는 tbody의 직접 자식 tr만 셉니다. 인덱스 1과 3인 r2, r4 | 2가 표시됩니다.
  2. Include header .odd()는 thead의 tr까지 포함합니다. 헤더가 인덱스 0을 차지하여 r1, r3, r5 | 3으로 달라집니다.
  3. Data :nth-child(odd)는 tbody 안에서 1, 3, 5번째 형제를 선택합니다. 결과는 r1, r3, r5 | 3이지만, 앞 버튼과 결과가 같다고 세는 규칙까지 같은 것은 아닙니다.
  4. Prepend one data row는 tbody 맨 앞에 Extra item 한 줄을 넣고 데이터 행의 .odd()를 다시 계산합니다. 기존 r1이 인덱스 1로 밀려 r1, r3, r5 | 3이 됩니다. 여러 번 눌러도 extra ID를 확인하여 한 줄만 추가합니다.
  5. Reset은 추가한 행만 제거하고 데이터 행의 .odd()를 재적용합니다. 원래 다섯 행과 값은 보존되며 처음의 r2, r4 | 2로 돌아갑니다.

show는 기존 marked 클래스를 모두 지운 뒤 새 결과에만 붙입니다. 따라서 조건을 바꿔도 이전 강조가 누적되지 않습니다. 실제 표에서는 페이지의 다른 표나 중첩 표까지 포함하지 않도록 ID와 직접 자식 관계로 범위를 제한합니다.

CSS의 odd와 직접 비교하기

처음 열었거나 Reset을 누른 상태에서 개발자 도구의 Console에 아래 부분 코드를 실행합니다. 첫 두 줄은 원래 선택자와 대안 메서드이고, 마지막 줄은 CSS 규칙을 따르는 :nth-child()입니다.

const ids = $set => $set.map((_, row) => row.id).get().join(", ");
console.log(ids($("#records > tbody > tr:odd")));
console.log(ids($("#records > tbody > tr").odd()));
console.log(ids($("#records > tbody > tr:nth-child(odd)")));
r2, r4
r2, r4
r1, r3, r5

.odd()는 선택된 집합 전체를 하나로 세지만 :nth-child(odd)는 부모마다 형제 위치를 다시 셉니다. tbody가 여러 개이면 후자는 각각 처음부터 계산합니다. :nth-child()는 다른 태그를 포함한 모든 요소 형제를 세고, 그 위치에서 앞의 tr 조건까지 맞는 요소를 남깁니다. 공백 텍스트나 주석은 요소 형제 수에 넣지 않습니다.

필터 순서·빈 결과·동적 행 처리

예제의 eligible 클래스는 r1, r3, r4, r5에 있습니다. $("#records > tbody > tr.eligible").odd()는 이 네 행을 먼저 모아 r3와 r5를 남깁니다. 반대로 데이터 다섯 행에서 .odd()를 적용한 뒤 .filter(".eligible")를 붙이면 r2와 r4 중 r4만 남습니다. 조건을 적용하는 순서가 인덱스의 기준 집합을 바꿉니다.

빈 집합이나 요소가 하나뿐인 집합에는 인덱스 1이 없으므로 .odd() 결과도 비어 있습니다. 선택 결과는 살아 있는 자동 갱신 목록이 아닙니다. 행 추가 전에 저장한 r2·r4 집합은 추가 후에도 그 요소를 유지하며, 현재 위치에 맞는 강조를 원하면 dataRows처럼 다시 조회해야 합니다.

동작하지 않으면 Network에서 jQuery 파일 로딩과 Console의 오류를 확인하고 jQuery.fn.jquery로 버전을 확인합니다. 결과가 예상과 반대면 헤더 포함 여부, tbody 범위, 필터 순서, 추가한 행이 있는지부터 점검합니다. 눈에 보이는 순번과 0부터 세는 인덱스를 구분하는 것이 핵심입니다.