jQuery / メソッド / .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()とは逆です。関数は真偽値を明示すると分かりやすくなります。新集合はコピーではなく既存ノードへの参照なので、.addClass()を呼ぶと実際の要素が変わります。

除外と文書内の数を比較する実習

全コードをUTF-8のnot-demo.htmlとして保存し、ブラウザーで開きます。コンテンツ配信ネットワーク(CDN)から公式jQueryファイル4.0.0を読むインターネット接続が必要ですが、インストールは不要です。セレクター・関数・参照、クラス変更、動的追加、初期化を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・2を除き、BetaとDeltaを残します。最初の項目がインデックス0です。
  3. Beta and Delta node referencesはその実際のノードをjQueryオブジェクトで渡し、AlphaとGammaを残します。
  4. Locked classに戻りToggle Alpha lockedを押すと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親が自動除外されるわけでもないので、検査対象と子孫を分けます。

結果は自動更新リストではありません。update()で毎回liを再選択して除くため、Epsilonと変更クラスが反映します。以前の結果が後の文書変化を自動追従するとは考えません。空でもjQueryオブジェクトはあるので.lengthで確認し、本当に削除したいなら後続の処理を別途決めます。