[JavaScript講座] 配列とコレクション

当ページのリンクには広告が含まれています。
目次

配列の復習

配列リテラルと基本

const arr = [10, 20, 30];

console.log(arr[0]);    // 10
console.log(arr.length); // 3
  • インデックスは 0 始まり
  • length は「最大インデックス+1」というイメージ
const a = [];
a[5] = 100;
console.log(a.length); // 6
console.log(a);        // [ <5 empty items>, 100 ]
  • 「穴あき配列(sparse array)」も作れてしまう → あまり推奨されない

破壊的 / 非破壊的メソッド

ここがかなり大事なので、まず定義をはっきりさせます。

  • 破壊的(ミューテート系):元の配列そのものを変更するメソッド
  • 非破壊的:新しい配列を返すだけで、元は変えないメソッド

「バグの温床」になりやすいのは、破壊的メソッドを無自覚に混ぜることです。

代表的な破壊的メソッド

要素の追加・削除系

const arr = [1, 2, 3];

arr.push(4);         // 末尾に追加 → [1, 2, 3, 4]
arr.pop();           // 末尾を削除 → [1, 2, 3]

arr.unshift(0);      // 先頭に追加 → [0, 1, 2, 3]
arr.shift();         // 先頭を削除 → [1, 2, 3]
  • push / pop は末尾で動くので比較的高速(実務でもよく使う)
  • shift / unshift は 全要素のインデックス詰め直しが必要なので、巨大配列で使用すると遅い

splice:任意位置の削除・挿入

const arr = [1, 2, 3, 4, 5];

// index 1 から 2個削除
const removed = arr.splice(1, 2);

console.log(arr);     // [1, 4, 5]
console.log(removed); // [2, 3]

挿入もできる:

const arr = [1, 4, 5];

arr.splice(1, 0, 2, 3); // index 1 に 2, 3 を挿入(削除数0)

console.log(arr); // [1, 2, 3, 4, 5]
  • 任意位置の「削除/挿入/置き換え」を一手に引き受ける万能メソッド
  • ただし破壊的かつ挙動が多機能で読みづらくなりがちなので、
    複雑に使いすぎない方が読みやすいコードになることが多いです。

並び替え・反転

const arr = [3, 1, 2];

arr.sort();       // 破壊的
console.log(arr); // [1, 2, 3]

arr.reverse();    // 破壊的
console.log(arr); // [3, 2, 1]

その他の破壊的

const arr = [1, 2, 3, 4];
arr.fill(0);            // 全部0にする → [0, 0, 0, 0]

const arr2 = [1, 2, 3, 4, 5];
arr2.copyWithin(1, 3);  // index3からの要素をindex1以降にコピー → [1,4,5,4,5]
  • fill, copyWithin は性能チューニング用途などで使うことがありますが、
    入門段階では「そういうのもあるんだ」くらいでOKです。

代表的な非破壊的メソッド

slice:部分配列の取得

const arr = [1, 2, 3, 4, 5];

const a = arr.slice(1, 4); // index1〜3 → [2, 3, 4]

console.log(arr); // 元はそのまま [1, 2, 3, 4, 5]
console.log(a);

concat:配列結合

const a = [1, 2];
const b = [3, 4];

const c = a.concat(b); // [1, 2, 3, 4]

最近はスプレッド構文([...a, ...b])で書くことも多いですが、
「concat は非破壊的」とだけ意識しておけば十分です。

探索系(非破壊)

const arr = [10, 20, 30, 40];

arr.indexOf(20);      // 1
arr.includes(30);     // true
arr.find(x => x > 25);      // 30
arr.findIndex(x => x > 25); // 2
  • どれも 元配列は変えない

新しめの「非破壊版」メソッド

実装環境によっては、次のような「非破壊版 sort/splice/reverse」も使えます:

const arr = [3, 1, 2];

const sorted = arr.toSorted();   // 非破壊版 sort
const reversed = arr.toReversed(); // 非破壊版 reverse
const spliced = arr.toSpliced(1, 1); // 非破壊版 splice
  • 方針としては「Arrayの破壊的メソッドに to が付いたら非破壊版」と覚えると楽です。

高階関数:forEach / map / filter / reduce

ここは超重要です。配列処理のほとんどはこれらで書けるようになります。

array.method((value, index, array) => { ... });

という形で「コールバック関数を引数に取るメソッド」を総称して、高階関数(higher-order function)と呼びます。

forEach:副作用ループ

const arr = [1, 2, 3];

arr.forEach((value, index, array) => {
  console.log(index, value);
});

// 0 1
// 1 2
// 2 3
  • 戻り値は常に undefined
  • 「配列を1周しながら副作用を起こす」用途(ログ出力、DOM操作など)

break / continue がしづらいので、
「途中で抜けたい」ループには for...of の方が向いている ことも多いです。

map:変換して「同じ長さの別配列」を作る

const arr = [1, 2, 3];

const doubled = arr.map(x => x * 2);
console.log(doubled); // [2, 4, 6]
console.log(arr);     // [1, 2, 3](非破壊)
  • 各要素を何かに「変換」したいときはまず map
  • 元と同じ長さの配列が返る(コールバックが undefined を返しても length は同じ)

filter:条件に合う要素だけ抽出

const arr = [1, 2, 3, 4, 5];

const even = arr.filter(x => x % 2 === 0);
console.log(even); // [2, 4]
console.log(arr);  // 元はそのまま
  • コールバックが true を返した要素だけ残る
  • 長さは変わる(0〜元の長さ)

reduce:1つの値に畳み込む

const arr = [1, 2, 3, 4];

const sum = arr.reduce((acc, value) => acc + value, 0);

console.log(sum); // 10

シグネチャ:

array.reduce((accumulator, currentValue, index, array) => {
  // 次のaccumulatorを返す
}, initialValue);
  • accumulator(畳み込み用の値)を持ちながら左から右へ1周する
  • initialValue を省略すると、配列の最初の要素が初期値になり、
    2番目の要素からコールバックが始まる

いろいろな用途:

  • 合計 / 積
  • オブジェクトや Map への変換
  • グルーピング

例:配列 → インデックス付きオブジェクト

const arr = ["apple", "banana", "orange"];

const map = arr.reduce((acc, value, index) => {
  acc[index] = value;
  return acc;
}, {});

console.log(map); // { 0: "apple", 1: "banana", 2: "orange" }

reduce は「何でもできる反面、読みづらくなりやすい」ので、
まずは sum などのシンプルな用途で慣れる のがおすすめです。

よく使う他の高階関数

この章のメイン外ですが、よく出てくるので名前と用途だけ紹介:

arr.some(fn);   // 1つでも true があれば true
arr.every(fn);  // 全部 true なら true
arr.find(fn);   // 条件を満たす最初の要素
arr.findIndex(fn); // そのインデックス

Set / Map / WeakSet / WeakMap

配列は「順序付きの要素リスト」ですが、他のコレクション型も重要です。

Set:重複なしの集合

const set = new Set();

set.add(1);
set.add(2);
set.add(2); // 無視される(重複不可)

console.log(set.has(2));  // true
console.log(set.size);    // 2

set.delete(1);
console.log(set.has(1));  // false
  • 順序は「追加順」を保ちます
  • 値の一意性は === に近いルール(ただし NaN は例外的に同一視)

配列との行き来

const arr = [1, 2, 2, 3];

const uniq = [...new Set(arr)]; // [1, 2, 3]

「配列の重複排除」の定番テクニックです。

ループ

const set = new Set([10, 20, 30]);

for (const value of set) {
  console.log(value);
}

Map:キーに何でも使える連想配列

オブジェクト {} との一番大きな違いは:

  • Map は「キーに任意の値(オブジェクトでもOK)」を使える
const m = new Map();

const objKey = { id: 1 };

m.set("name", "Taro");
m.set(objKey, "data");

console.log(m.get("name"));    // "Taro"
console.log(m.get(objKey));    // "data"
console.log(m.size);           // 2
console.log(m.has("name"));    // true

m.delete("name");
m.clear(); // 全削除

オブジェクト {} と違う点

const o = {};
const k1 = { id: 1 };
const k2 = { id: 1 };

o[k1] = "A";
console.log(o[k2]); // "A"(キーは "[object Object]" に文字列化される)

const m = new Map();
m.set(k1, "A");
console.log(m.get(k2)); // undefined(別オブジェクトとして扱われる)
  • オブジェクトのキーは基本 文字列(と Symbol) に変換される
  • Map のキーは「値そのものの参照」で管理される

ループ

const m = new Map([
  ["a", 1],
  ["b", 2],
]);

for (const [key, value] of m) {
  console.log(key, value);
}

// entries, keys, values もある
for (const key of m.keys()) { ... }
for (const value of m.values()) { ... }

WeakSet / WeakMap(GCフレンドリーな特殊コレクション)

共通の特徴

  • キー(WeakSet の場合は要素)は「オブジェクト限定」
  • 「弱い参照」:
    → そのオブジェクトを参照しているのが WeakX だけになったら、
    ガーベジコレクタに回収される
  • 内容を列挙する API がない(keys / values などが無い)

これは「メモリリークを防ぎながらキャッシュしたい」ようなシナリオ向けです。

WeakMap のイメージ例

const wm = new WeakMap();

function track(obj) {
  wm.set(obj, { visited: true });
}

let user = { name: "Taro" };
track(user);

// ここで user を null にすると、
// user オブジェクトは(他に参照がなければ)GCされる可能性がある。
// WeakMap が参照していても、GCされる。
user = null;
  • 通常の Map だと、Map 自体が強参照を持っているので GC されない
  • WeakMap なら、「気づかないうちにキャッシュが溜まり続けてメモリを食う」という問題を軽減できる

WeakSet

  • オブジェクトの集合だが、GCを妨ぎたくない」ケース
const visited = new WeakSet();

function visit(node) {
  if (visited.has(node)) {
    return;
  }
  visited.add(node);
  // 何らかの処理...
}
  • DOM ノードや大きなオブジェクトなどに「訪問済みフラグ」を立てるのに便利
  • 参照が他に無くなれば自動的にGCされるので、
    visited がリークの原因になりにくい

実務的な「使い分け」の感覚

ざっくりしたガイドラインとして:

  • 配列(Array)
    • 順序が大事
    • 同じ型の値が多く並ぶケース
    • 添字アクセス / 並び順通りに処理する
  • オブジェクト {}
    • 「名前付きのプロパティをひとまとめにする」レコード的用途
    • JSON と相性が良い(そのまま JSON.stringify できる)
  • Set
    • 「一意な要素集合」を扱いたい(重複排除)
    • 「集合演算」(和・差など)をやりたいときのベース
  • Map
    • キーが文字列以外(オブジェクトなど)である必要がある
    • キーの数を .size で素直に取りたい
    • Object.prototype の影響などを避けたい
  • WeakSet / WeakMap
    • 「大きなオブジェクトをキーにしたキャッシュ」
    • 「訪問済みフラグ」などでメモリリークしたくない場面
      (仕様理解として押さえておけば十分で、最初から多用するものではない)

<<前へ(文字列・数値・数学)

>>次へ(日付・正規表現・国際化(Date / JSON / RegExp / Intl))

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次