目次
配列の復習
配列リテラルと基本
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
- 「大きなオブジェクトをキーにしたキャッシュ」
- 「訪問済みフラグ」などでメモリリークしたくない場面
(仕様理解として押さえておけば十分で、最初から多用するものではない)
コメント