jQuery / メソッド / .slice() - インデックス範囲を選択する

jQueryの.slice(start, end)は現在の集合からstartを含みendを含まない範囲を新しいjQueryオブジェクトで返します。インデックスは0からで、endを省略すると最後まで選びます。残りの要素を元の集合や文書から削除するメソッドではありません。

範囲と負のインデックス

.slice()の仕様の番号は現在集合の順序で、HTMLの親内の兄弟位置ではありません。Alpha・Beta・Gamma・Delta・Epsilon・Zetaは0〜5です。負数は末尾から数え、-1は最後の位置です。

呼び出し 6項目での結果 理由
.slice(1, 4) Beta・Gamma・Delta 1を含み4を除く
.slice(2) GammaからZeta 終了省略
.slice(-2) Epsilon・Zeta 後ろから二つ目以降
.slice(1, -1) BetaからEpsilon 最後の位置を除く
.slice(3, 3)・.slice(4, 2) 空集合 含める前向き範囲がない

endは個数ではありません。.slice(1, 4)は1・2・3の3個です。単一なら.eq(index)も使えます。条件で選ぶ.filter()とは基準が違います。

範囲とフィルター順序を比較する完全な例

全コードをUTF-8のslice-demo.htmlに保存してブラウザーで開きます。コンテンツ配信ネットワーク(CDN)の公式jQueryファイル4.0.0を読むインターネットが必要ですが、インストールは不要です。範囲、フィルター順序、追加、初期化とConsoleコードを3.7.1・4.0.0で実行しました。

eligibleは条件一致を示す例のクラスです。初期6項目の選択範囲を青緑で強調し、非選択項目も文書に残します。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Select a range from a 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; max-width: 100%; }
    li { border: 1px solid #aab; padding: 8px; margin: 4px; }
    .picked { background: #d9f4ef; border-color: teal; }
    pre { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <h1>Select a collection range</h1>
  <label for="mode">Range:</label>
  <select id="mode">
    <option value="middle">slice(1, 4)</option>
    <option value="last">slice(-2)</option>
    <option value="inner">slice(1, -1)</option>
    <option value="from">slice(2)</option>
    <option value="filterFirst">filter('.eligible').slice(1, 3)</option>
    <option value="sliceFirst">slice(1, 3).filter('.eligible')</option>
    <option value="empty">slice(4, 2)</option>
  </select>
  <button type="button" id="add">Add Eta</button>
  <button type="button" id="reset">Reset</button>
  <ul id="items">
    <li class="eligible" data-name="Alpha">Alpha (eligible)</li>
    <li data-name="Beta">Beta</li>
    <li class="eligible" data-name="Gamma">Gamma (eligible)</li>
    <li data-name="Delta">Delta</li>
    <li class="eligible" data-name="Epsilon">Epsilon (eligible)</li>
    <li class="eligible" data-name="Zeta">Zeta (eligible)</li>
  </ul>
  <pre id="result" aria-live="polite"></pre>
  <script>
    $(function () {
      const initial = $("#items").html();
      function report() {
        const $all = $("#items > li");
        let $selected;
        switch ($("#mode").val()) {
          case "last": $selected = $all.slice(-2); break;
          case "inner": $selected = $all.slice(1, -1); break;
          case "from": $selected = $all.slice(2); break;
          case "filterFirst": $selected = $all.filter(".eligible").slice(1, 3); break;
          case "sliceFirst": $selected = $all.slice(1, 3).filter(".eligible"); break;
          case "empty": $selected = $all.slice(4, 2); break;
          default: $selected = $all.slice(1, 4);
        }
        $all.removeClass("picked");
        $selected.addClass("picked");
        const names = $selected.map(function () { return this.dataset.name; }).get();
        $("#result").text(`source=${$all.length}\nselected=${$selected.length}: ${names.join(", ") || "(none)"}\nDOM items=${$("#items > li").length}`);
      }
      $("#mode").on("change", report);
      $("#add").on("click", function () {
        if (!$("#eta").length) $("#items").append('<li id="eta" class="eligible" data-name="Eta">Eta (eligible)</li>');
        report();
      });
      $("#reset").on("click", function () { $("#items").html(initial); $("#mode").val("middle"); report(); });
      report();
    });
  </script>
</body>
</html>

初めは.slice(1, 4)の3項目で、source・DOM itemsは6のままです。

source=6
selected=3: Beta, Gamma, Delta
DOM items=6
  1. slice(-2)はEpsilon・Zeta、slice(1, -1)はBeta・Gamma・Delta・Epsilonです。Resetして条件を一つずつ比べます。
  2. filter('.eligible').slice(1, 3)は先にAlpha・Gamma・Epsilon・Zetaに絞り、その1・2のGamma・Epsilonを選びます。
  3. slice(1, 3).filter('.eligible')は元のBeta・Gammaを選んでから条件適用しGammaだけです。先行集合が番号の基準を変えます。
  4. slice(4, 2)は0項目ですが、元6項目は削除しません。
  5. slice(-2)でAdd Etaを押すとZeta・Etaになります。report()が7項目を再選択するためです。重複追加は防ぎます。
  6. Resetはリストと.slice(1, 4)モードを戻します。

保持したconst $last = $("#items > li").slice(-2)は追加に合わせ自動更新されず、元のEpsilon・Zetaを参照し続けます。現在の区間にはDOM(Document Object Model、文書オブジェクトモデル)変更後に再選択します。

境界とページ区間を確認する

Reset後、開発者ツールConsoleで次を実行します。過大な境界は長さの範囲へ調整され、末尾を超える開始は空です。最後の行は新しい集合でもノードは複製でなく同じ参照と確認します。

const $items = $("#items > li");
const names = $set => $set.map(function () { return this.dataset.name; }).get().join(",");
console.log(`slice(1,4)=${names($items.slice(1, 4))}`);
console.log(`slice(-2)=${names($items.slice(-2))}`);
console.log(`slice(1,-1)=${names($items.slice(1, -1))}`);
console.log(`equal bounds=${$items.slice(3, 3).length}; beyond end=${$items.slice(10).length}`);
console.log(`clamped range=${$items.slice(-99, 99).length}; original=${$items.length}`);
console.log(`same node=${$items.slice(1, 2).get(0) === $items.get(1)}`);
slice(1,4)=Beta,Gamma,Delta
slice(-2)=Epsilon,Zeta
slice(1,-1)=Beta,Gamma,Delta,Epsilon
equal bounds=0; beyond end=0
clamped range=6; original=6
same node=true

ページ番号を1から、ページサイズを正の整数にすれば、開始は(page - 1) × pageSize、終了はstart + pageSizeです。同じConsoleで次を実行すると2ページ目の2項目を選びます。負ページがsliceの後ろ向き位置として解釈されないよう先に検証します。

const page = 2;
const pageSize = 2;
if (!Number.isInteger(page) || page < 1 || !Number.isInteger(pageSize) || pageSize < 1) {
  throw new RangeError("Use positive integer page and pageSize");
}
const start = (page - 1) * pageSize;
const $pageItems = $("#items > li").slice(start, start + pageSize);
console.log(`page=${page}: ${$pageItems.map(function () { return this.dataset.name; }).get().join(",")}`);
page=2: Gamma,Delta

区間計算だけで他項目を隠したりサーバーから取得したりはしません。実ページ切り替えには表示・総数・現在ページ管理が別途必要です。読み込み済み項目の切り出しだけでサーバーページ処理や転送量が減ったとは説明しません。

予想外の項目が選ばれるとき

まず.lengthと集合順を確認します。限定選択子や先行.filter()で基準も変わります。兄弟位置のCSS番号や1始まりと混同しません。最後の希望位置を含めるにはその番号+1をendにします。

slice自体は複製・移動・削除をしませんが、結果への.addClass()・.text()は共有する実ノードを変えます。独立ノードなら別途意図的に複製します。空のjQueryオブジェクトも真として評価されるため、有無は.lengthで確認します。