jQuery / Method / .not() - 선택한 요소 중 특정 선택자를 제외한 요소를 선택하는 메서드

jQuery의 .not()은 현재 선택 집합에서 조건에 맞는 요소를 제외한 새 jQuery 객체를 반환합니다. 제외된 요소를 DOM(Document Object Model, 문서 객체 모델)에서 지우거나 자동으로 숨기지는 않습니다. 함수형 조건에서는 true를 반환한 요소가 결과에서 빠집니다.

not은 제외하고 filter는 남깁니다

.not() 공식 문서는 선택자 문자열, DOM 요소·배열, jQuery 객체와 함수를 인수로 받는 형식을 설명합니다. 모든 형식은 이미 선택한 집합의 구성원을 검사합니다. 선택한 요소 안에서 새로운 자손을 찾는 동작과는 다릅니다.

표현식 결과에 남는 요소 DOM 제거
$all.not(".locked") locked 클래스가 없는 요소 하지 않음
$all.filter(".locked") locked 클래스가 있는 요소 하지 않음
$all.not(".locked, .featured") 두 조건 중 어느 것에도 해당하지 않는 요소 하지 않음
$all.not(function) 함수가 false로 판정하는 요소 하지 않음

.filter()는 일치하는 요소를 남기므로 같은 조건의 .not()과 방향이 반대입니다. 함수에서 boolean을 명시적으로 반환하면 의도가 분명합니다. 새 집합은 노드 복사본이 아니라 기존 노드의 참조를 담으므로, 그 결과에 .addClass()를 호출하면 실제 같은 요소의 외관이 바뀝니다.

제외 조건과 DOM 개수를 비교하는 실습

전체 코드를 UTF-8의 not-demo.html로 저장하고 브라우저에서 엽니다. 공식 jQuery 파일 4.0.0을 CDN(Content Delivery Network, 콘텐츠 전송 네트워크)에서 읽을 인터넷 연결이 필요하며 별도 설치는 없습니다. 선택자·함수·참조 비교, 클래스 변경·동적 추가·초기화를 3.7.1과 4.0.0에서 실행했습니다.

Alpha는 일반 항목, Beta는 locked, Gamma는 featured, Delta는 두 클래스 모두를 가집니다. 남은 요소만 picked 클래스로 강조하며 제외된 항목도 화면에 그대로 보여 줍니다. source는 원래 집합, kept는 결과 집합, in DOM은 문서 안의 항목 수입니다.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Exclude elements from a jQuery collection</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; }
    li { padding: 8px; margin: 4px; border: 1px solid #aab; }
    .picked { background: #d9f4ef; border-color: teal; }
    pre { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <h1>Filter a collection without removing nodes</h1>
  <label for="mode">Exclude:</label>
  <select id="mode"><option value="locked">Locked class</option><option value="multi">Locked or featured</option><option value="callback">Even collection indices</option><option value="nodes">Beta and Delta node references</option></select>
  <button type="button" id="toggle">Toggle Alpha locked</button>
  <button type="button" id="add">Add Epsilon</button>
  <button type="button" id="reset">Reset</button>
  <ul id="items"><li id="alpha" data-name="Alpha">Alpha</li><li id="beta" class="locked" data-name="Beta">Beta (locked)</li><li id="gamma" class="featured" data-name="Gamma">Gamma (featured)</li><li id="delta" class="locked featured" data-name="Delta">Delta (locked, featured)</li></ul>
  <pre id="result" aria-live="polite"></pre>
  <script>
    $(function () {
      const initial = $("#items").html();
      function update() {
        const $all = $("#items > li");
        let $kept;
        switch ($("#mode").val()) {
          case "multi": $kept = $all.not(".locked, .featured"); break;
          case "callback": $kept = $all.not(function (index) { return index % 2 === 0; }); break;
          case "nodes": $kept = $all.not($("#beta, #delta")); break;
          default: $kept = $all.not(".locked");
        }
        $all.removeClass("picked");
        $kept.addClass("picked");
        const names = $kept.map(function () { return this.dataset.name; }).get();
        $("#result").text(`source=${$all.length} | kept=${$kept.length} | in DOM=${$("#items > li").length}\nkept=${names.join(", ")}`);
      }
      $("#mode").on("change", update);
      $("#toggle").on("click", function () { $("#alpha").toggleClass("locked"); update(); });
      $("#add").on("click", function () {
        if (!$("#epsilon").length) $('<li id="epsilon" data-name="Epsilon">Epsilon</li>').appendTo("#items");
        update();
      });
      $("#reset").on("click", function () { $("#items").html(initial); $("#mode").val("locked"); update(); });
      update();
    });
  </script>
</body>
</html>

처음에는 locked를 제외해 Alpha와 Gamma가 남습니다.

source=4 | kept=2 | in DOM=4
kept=Alpha, Gamma
  1. Locked or featured를 선택하면 Alpha만 강조됩니다. 쉼표는 두 조건 중 하나라도 일치하면 제외한다는 뜻입니다.
  2. Even collection indices는 0부터 세는 인덱스 0·2를 제외해 Beta와 Delta를 남깁니다. 화면의 첫 번째 항목이 인덱스 0입니다.
  3. Beta and Delta node references는 실제 두 노드의 jQuery 객체를 넘겨 Alpha와 Gamma를 남깁니다.
  4. Locked class로 돌아가 Toggle Alpha locked를 누르면 Alpha도 제외되어 Gamma만 강조됩니다. 버튼을 다시 누르면 Alpha가 돌아옵니다.
  5. Add Epsilon은 일반 항목을 한 번 추가합니다. Alpha가 locked인 상태에서는 Gamma와 Epsilon이 남으며 source와 in DOM은 5입니다.
  6. Reset은 처음 네 항목과 클래스를 복원하고 조건을 Locked class로 돌립니다.

함수의 인덱스와 참조 비교

.not(function(index, element))의 index는 현재 jQuery 집합에서 0부터 시작하는 위치입니다. 부모 안의 몇 번째 자식인지와 동일하다고 가정하지 않습니다. 일반 function 안의 this와 element는 검사 중인 DOM 요소입니다. 조건이 true면 제외되므로 ‘남기려는 조건’을 그대로 반환하지 않도록 주의합니다.

다음 완전한 부분 코드를 개발자 도구 Console에서 실행합니다. 문서에 넣지 않은 임시 i 요소 세 개로 not과 filter, 함수형 제외, 실제 노드와 복사본의 차이를 비교합니다. 마지막 줄들은 임시 노드를 정리합니다.

const $items = $('<i class="skip">A</i><i>B</i><i class="skip">C</i>');
console.log(`not=${$items.not(".skip").text()} | filter=${$items.filter(".skip").text()}`);
console.log(`source=${$items.length} | callback=${$items.not(function (index) { return index === 1; }).text()}`);
const $copy = $items.eq(1).clone();
console.log(`exclude same node=${$items.not($items.eq(1)).length} | exclude copy=${$items.not($copy).length}`);
$copy.remove();
$items.remove();
not=B | filter=AC
source=3 | callback=AC
exclude same node=2 | exclude copy=3

.not() 호출 후에도 원래 $items.length는 3입니다. 두 번째 노드 자체를 제외하면 2개가 남지만, 모양과 글자가 같은 clone을 넘겨도 원래 노드와 다른 객체이므로 3개가 모두 남습니다. 다른 코드에서 얻은 노드를 제외할 때는 선택자 문자열의 유사성이 아니라 실제 대상 참조가 맞는지 확인합니다.

선택자 범위와 동적 상태 확인하기

단순한 선택자 조건은 :not() 선택자로도 표현할 수 있습니다. 예를 들어 #items > li:not(.locked)는 같은 범위의 li에서 locked를 빼는 표현입니다. 여러 조건과 저장한 참조·함수가 섞이면 먼저 기본 집합을 정한 뒤 .not()으로 조건을 나누는 편이 읽기 쉽습니다.

.not(".locked, .featured")는 둘 중 하나를 제외하고, .not(".locked.featured")는 두 클래스를 동시에 가진 요소만 제외합니다. 점 없이 locked라고 쓰면 클래스가 아니라 태그 이름이 됩니다. 자식 span의 조건을 적었다고 li 자체가 자동으로 제외되는 것도 아니므로, 검사할 대상이 li인지 그 자손인지 구분합니다.

결과 집합은 자동 갱신되는 목록이 아닙니다. 예제는 update()마다 다시 li를 선택하고 제외하므로 새 Epsilon과 바뀐 클래스가 반영됩니다. 미리 보관한 결과가 이후 문서나 상태 변화에 맞춰 자동으로 바뀐다고 가정하지 않습니다. 결과가 비어도 새 jQuery 객체는 존재하므로 .length로 남은 개수를 확인하고, 실제 제거가 목적이었다면 .not() 이후 무엇을 수행할지 따로 정합니다.