jQuery / Method / .not() - Exclude Elements from a Selection

jQuery's .not() returns a new jQuery object excluding matching members of the current selection. It does not remove excluded elements from the Document Object Model (DOM) or hide them automatically. With a callback, elements returning true are excluded.

not excludes while filter retains

The .not() API accepts selector strings, DOM elements or arrays, jQuery objects and functions. Every form tests members of a selection already made. It does not find new descendants inside those elements.

Expression Members retained DOM removal
$all.not(".locked") Elements without locked No
$all.filter(".locked") Elements with locked No
$all.not(".locked, .featured") Elements matching neither condition No
$all.not(function) Elements for which the test is false No

.filter() keeps matches, the opposite direction from .not() for the same test. Returning an explicit boolean makes a callback clearer. The new collection contains references to existing nodes, not node copies; .addClass() on it therefore changes those actual elements.

Compare exclusion with document counts

Save the complete code as UTF-8 in not-demo.html and open it in a browser. Internet is needed for the official jQuery file 4.0.0 from its content delivery network (CDN); no installation is required. Selector, callback and reference tests, class changes, dynamic addition and reset were executed with 3.7.1 and 4.0.0.

Alpha is ordinary, Beta locked, Gamma featured and Delta both. Only retained members receive picked highlighting; excluded items stay visible. source counts the original collection, kept the result and in DOM the document's items.

<!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>

Initially, excluding locked retains Alpha and Gamma:

source=4 | kept=2 | in DOM=4
kept=Alpha, Gamma
  1. Locked or featured highlights only Alpha. The comma excludes anything matching either condition.
  2. Even collection indices excludes indices 0 and 2, leaving Beta and Delta. The first visible entry has index 0.
  3. Beta and Delta node references passes those actual nodes in a jQuery object, leaving Alpha and Gamma.
  4. Return to Locked class and press Toggle Alpha locked. Only Gamma remains highlighted; press again to restore Alpha.
  5. Add Epsilon adds an ordinary item once. With Alpha locked, Gamma and Epsilon remain and source/in DOM become 5.
  6. Reset restores the initial four items/classes and Locked class mode.

Callback indices and node identity

In .not(function(index, element)), index is a zero-based position in the current jQuery collection, not necessarily the sibling position within a parent. In an ordinary function, this and element identify the DOM node under test. True excludes it, so do not accidentally return the condition for members you wanted to keep.

Run this complete fragment in the developer tools Console. Three temporary i elements, not inserted into the document, compare not/filter, callback exclusion and actual nodes versus copies. The last lines clean them up.

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

The original $items.length stays 3 after .not(). Excluding the second node itself leaves two. Passing its identical-looking clone leaves all three because the clone is another object. For references obtained elsewhere, verify the actual target node rather than similarity of selector text or markup.

Selector scope and changing state

A simple string condition can also use the :not() selector. For instance, #items > li:not(.locked) excludes locked from the same li scope. With several conditions, saved references or functions, defining a base collection first and splitting tests into .not() calls can be easier to read.

.not(".locked, .featured") excludes either class; .not(".locked.featured") excludes only elements having both. Without a dot, locked is a tag name. A child span condition also does not automatically exclude its li parent; distinguish the members under test from their descendants.

The result is not a live list. update() makes a fresh li selection and exclusion each time, so Epsilon and changed classes are reflected. Earlier saved results do not automatically follow later document changes. Even an empty result is a jQuery object: check .length, and decide separately what action follows if actual removal is your goal.