JavaScriptの基礎 - 配列のソートと変換
概要
JavaScriptの配列には、要素の順序を変更するためのソートメソッドや、配列の形状を変換するためのメソッドが豊富に用意されている。
これらを活用することで、データの並び替え、構造変換、文字列への変換といった処理を簡潔に記述できる。
sort() は配列を破壊的にソートするメソッドであり、デフォルトでは要素をUnicodeコードポイント順に並べる。
数値のソートや文字列の言語特有の順序を扱う場合は、比較関数を明示的に指定する必要がある。
ES2023では、sort() や reverse() の非破壊版にあたる toSorted()・toReversed()・toSpliced()・with() が追加された。
これらのメソッドは元の配列を変更せず新しい配列を返すため、Reactの状態管理のようなイミュータブルな更新パターンとの相性が良い。
flat() はネストした配列を平坦化するメソッドであり、Array.from() はイテラブルや配列風オブジェクトから新しい配列を生成する汎用的なメソッドである。
join() や concat() は配列を文字列や他の配列と組み合わせるための変換メソッドである。
また、Array.isArray() は値が配列かどうかを確実に判定する静的メソッドであり、typeof や instanceof が抱える問題を回避できる。
sort
sort() は配列の要素をソートするメソッドである。
元の配列を直接変更する破壊的なメソッドであり、戻り値はソート後の元の配列への参照である。
デフォルトの動作と注意点
比較関数を指定しない場合、sort() は全ての要素を文字列に変換した上で、UNICODEコードポイント順に並べる。
この動作は文字列の配列に対しては概ね期待通りに動作するが、数値の配列に対しては意図しない結果になることがある。
// 文字列配列のソート
const fruits = ["banana", "apple", "cherry"];
fruits.sort();
console.log(fruits); // ["apple", "banana", "cherry"]
// 注意 : 数値配列でのデフォルトの動作
const numbers = [10, 9, 80, 1, 5];
numbers.sort();
console.log(numbers); // [1, 10, 5, 80, 9] (文字列として比較されるため意図しない順序になる)
数値配列をソートするには、比較関数を明示的に指定する必要がある。
比較関数
sort() に比較関数を渡すことで、任意のソート順序を定義できる。
比較関数は2つの引数 a と b を受け取り、以下の規則に従って数値を返す必要がある。
| 戻り値 | 意味 |
|---|---|
| 負の値 | aをbより前に配置する |
| 0 | a と b の順序を変更しない |
| 正の値 | aをbより後に配置する |
数値のソート
数値配列を正しくソートするには、比較関数として (a, b) => a - b (昇順) または (a, b) => b - a (降順) を使用する。
なお、sort() は破壊的なメソッドであるため、元の配列を保持したい場合はスプレッド構文 [...numbers] でコピーしてからソートする。
const numbers = [10, 9, 80, 1, 5];
// 昇順ソート
const ascending = [...numbers].sort((a, b) => a - b);
console.log(ascending); // [1, 5, 9, 10, 80]
// 降順ソート
const descending = [...numbers].sort((a, b) => b - a);
console.log(descending); // [80, 10, 9, 5, 1]
文字列のソート (localeCompare)
日本語や他の言語を含む文字列配列を正しくソートするには、localeCompare() を使用する。
localeCompare() は言語特有の文字順序を考慮した比較を行う。
// 通常の比較では日本語のソートが不正確になる場合がある
const items = ["東京", "大阪", "名古屋", "札幌"];
items.sort((a, b) => a.localeCompare(b, "ja"));
console.log(items); // ロケール順に並んだ結果
// アルファベットの大文字・小文字を区別しないソート
const words = ["Banana", "apple", "Cherry"];
words.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }));
console.log(words); // ["apple", "Banana", "Cherry"]
オブジェクト配列のソート
オブジェクトの配列をソートするには、比較関数内でソートの基準にするプロパティを参照する。
ES2019以降、sort() は安定ソート (同じ比較結果の要素の元の順序を保持する) が仕様として保証されている。
const users = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
{ name: "Carol", age: 35 }
];
// ageプロパティで昇順ソート
users.sort((a, b) => a.age - b.age);
console.log(users);
// [{ name: "Bob", age: 25 }, { name: "Alice", age: 30 }, { name: "Carol", age: 35 }]
// nameプロパティでアルファベット順ソート
users.sort((a, b) => a.name.localeCompare(b.name));
console.log(users);
// [{ name: "Alice", age: 30 }, { name: "Bob", age: 25 }, { name: "Carol", age: 35 }]
reverse
reverse() は配列の要素の順序を反転するメソッドである。
sort() と同様に破壊的なメソッドであり、元の配列を直接変更する。
戻り値は反転後の元の配列への参照である。
const arr = [1, 2, 3, 4, 5];
// reverseは元の配列を変更する
const reversed = arr.reverse();
console.log(arr); // [5, 4, 3, 2, 1] (元の配列が変更される)
console.log(reversed === arr); // true (戻り値は元の配列への参照)
// 元の配列を保持したい場合はコピーしてから操作する
const original = [1, 2, 3, 4, 5];
const copy = [...original].reverse();
console.log(original); // [1, 2, 3, 4, 5] (変更されない)
console.log(copy); // [5, 4, 3, 2, 1]
// sort()と組み合わせて降順ソートを実現する例
const numbers = [3, 1, 4, 1, 5, 9];
const sortedDesc = [...numbers].sort((a, b) => a - b).reverse();
console.log(sortedDesc); // [9, 5, 4, 3, 1, 1]
ES2023の非破壊メソッド
ES2023では、破壊的な配列メソッドに対応する非破壊版が導入された。
これらのメソッドは元の配列を変更せず、新しい配列を返す。
ReactやVue等のフレームワークでは、状態 (state) をイミュータブルに更新することが推奨されている。
従来は、[...state].sort(...) のようにスプレッド構文で配列をコピーしてから操作する必要があったが、これらのメソッドを使用することでより簡潔に記述できる。
Webブラウザ対応状況を以下に示す。
- Chrome / Edge 110以降
- Safari 16以降
- Firefox 115以降
- Node.js 20以降
toSorted
toSorted(compareFn) は sort() の非破壊版である。
元の配列を変更せず、ソート済みの新しい配列を返す。
const numbers = [3, 1, 4, 1, 5, 9];
// toSortedは元の配列を変更しない
const sorted = numbers.toSorted((a, b) => a - b);
console.log(numbers); // [3, 1, 4, 1, 5, 9] (変更されない)
console.log(sorted); // [1, 1, 3, 4, 5, 9]
// Reactの状態管理での活用例
// 従来の方法
// setState(prevState => [...prevState].sort((a, b) => a - b));
// toSortedを使用した方法
// setState(prevState => prevState.toSorted((a, b) => a - b));
toReversed
toReversed() は reverse() の非破壊版である。
元の配列を変更せず、逆順の新しい配列を返す。
const arr = [1, 2, 3, 4, 5];
// toReversedは元の配列を変更しない
const reversed = arr.toReversed();
console.log(arr); // [1, 2, 3, 4, 5] (変更されない)
console.log(reversed); // [5, 4, 3, 2, 1]
toSpliced
toSpliced(start, deleteCount, ...items) は splice() の非破壊版である。
元の配列を変更せず、指定した位置の要素を削除・置換・追加した新しい配列を返す。
const arr = ["a", "b", "c", "d", "e"];
// 要素の削除 (インデックス1から2要素削除)
const deleted = arr.toSpliced(1, 2);
console.log(arr); // ["a", "b", "c", "d", "e"] (変更されない)
console.log(deleted); // ["a", "d", "e"]
// 要素の置換 (インデックス2の要素を "X", "Y" に置換)
const replaced = arr.toSpliced(2, 1, "X", "Y");
console.log(replaced); // ["a", "b", "X", "Y", "d", "e"]
// 要素の追加 (インデックス2に "X" を挿入)
const inserted = arr.toSpliced(2, 0, "X");
console.log(inserted); // ["a", "b", "X", "c", "d", "e"]
with
with(index, value) は、指定したインデックスの要素を新しい値に置き換えた新しい配列を返す。
元の配列は変更されない。
負のインデックスを使用して末尾からの位置を指定することもできる。
const arr = [1, 2, 3, 4, 5];
// インデックス2の要素を99に置き換える
const updated = arr.with(2, 99);
console.log(arr); // [1, 2, 3, 4, 5] (変更されない)
console.log(updated); // [1, 2, 99, 4, 5]
// 負のインデックスも使用可能
const last = arr.with(-1, 100);
console.log(last); // [1, 2, 3, 4, 100]
// Reactの状態管理でのイミュータブルな更新例
// 従来の方法
// setState(prevState => prevState.map((item, i) => i === index ? newValue : item));
// withを使用した方法
// setState(prevState => prevState.with(index, newValue));
flat
flat(depth) は、ネストした配列を指定した深さまで平坦化した新しい配列を返す。
元の配列は変更されない非破壊的なメソッドである。
const nested = [1, [2, 3], [4, [5, 6]]];
// デフォルトは1段階のみ平坦化
console.log(nested.flat()); // [1, 2, 3, 4, [5, 6]]
// 深さを指定して平坦化
console.log(nested.flat(2)); // [1, 2, 3, 4, 5, 6]
// Infinity で全ての深さを平坦化
const deepNested = [1, [2, [3, [4, [5]]]]];
console.log(deepNested.flat(Infinity)); // [1, 2, 3, 4, 5]
// 空要素の除去にも使用できる
const sparse = [1, , 2, , 3];
console.log(sparse.flat()); // [1, 2, 3]
flatMap(fn) は map() と flat(1) を組み合わせたメソッドである。
各要素にマッピング関数を適用した後に1段階だけ平坦化した新しい配列を返す。
const sentences = ["Hello World", "Foo Bar"];
// mapとflatを組み合わせた場合
const words1 = sentences.map(s => s.split(" ")).flat();
console.log(words1); // ["Hello", "World", "Foo", "Bar"]
// flatMapを使用した場合 (同じ結果を簡潔に記述)
const words2 = sentences.flatMap(s => s.split(" "));
console.log(words2); // ["Hello", "World", "Foo", "Bar"]
// 条件により要素数を変えるフィルタリングと変換の組み合わせ
const numbers = [1, 2, 3, 4, 5];
const result = numbers.flatMap(n => n % 2 === 0 ? [n, n * 2] : []);
console.log(result); // [2, 4, 4, 8]
Array.from
Array.from() は、イテラブルオブジェクトまたは配列風オブジェクトから新しい配列を生成する静的メソッドである。
基本的な使用方法
Array.from() は以下のような入力から配列を生成できる。
// 文字列から配列を生成 (イテラブル)
console.log(Array.from("Hello")); // ["H", "e", "l", "l", "o"]
// Setから配列を生成 (重複排除に活用)
const set = new Set([1, 2, 2, 3, 3]);
console.log(Array.from(set)); // [1, 2, 3]
// Mapから配列を生成
const map = new Map([["a", 1], ["b", 2]]);
console.log(Array.from(map)); // [["a", 1], ["b", 2]]
// NodeList (DOMのquerySelectorAll等) から配列を生成
// const nodeList = document.querySelectorAll("div");
// const divArray = Array.from(nodeList);
// 配列風オブジェクト (lengthプロパティを持つオブジェクト) から配列を生成
const arrayLike = { 0: "a", 1: "b", 2: "c", length: 3 };
console.log(Array.from(arrayLike)); // ["a", "b", "c"]
第2引数のマッピング関数
Array.from() の第2引数にマッピング関数を渡すことで、配列生成と同時に要素を変換できる。
Array.from({length: n}, fn) のパターンは、連番配列や特定のパターンを持つ配列を生成する際に広く使われる。
// 0から4までの連番配列を生成
const range = Array.from({ length: 5 }, (_, i) => i);
console.log(range); // [0, 1, 2, 3, 4]
// 1から5までの連番配列を生成
const range1to5 = Array.from({ length: 5 }, (_, i) => i + 1);
console.log(range1to5); // [1, 2, 3, 4, 5]
// 文字列配列を変換しながら生成
const doubled = Array.from([1, 2, 3], x => x * 2);
console.log(doubled); // [2, 4, 6]
// 二次元配列の生成
const matrix = Array.from({ length: 3 }, (_, i) =>
Array.from({ length: 3 }, (_, j) => i * 3 + j + 1)
);
console.log(matrix); // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Array.isArray
Array.isArray(value) は、引数が配列かどうかを判定する静的メソッドである。
配列であれば true、そうでなければ false を返す。
// 配列の判定
console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray([])); // true
// 配列でないものの判定
console.log(Array.isArray("string")); // false
console.log(Array.isArray(123)); // false
console.log(Array.isArray({})); // false
console.log(Array.isArray(null)); // false
console.log(Array.isArray(undefined)); // false
下表に、typeof 演算子や instanceof 演算子を使用した判定と比較した場合の違いを示す。
| 方法 | 結果の例 | 問題点 |
|---|---|---|
typeof [] |
"object" を返す |
配列かオブジェクトかを区別できない。 |
[] instanceof Array |
true を返す |
iframe等のクロスフレーム環境で失敗することがある。 |
Array.isArray([]) |
true を返す |
全てのコンテキストで正確に動作する。 |
Array.isArray() はどの実行コンテキストでも正確に動作するため、配列の判定には Array.isArray() を使用することを推奨する。
join と toString
join(separator) は、配列の全要素を結合して1つの文字列にするメソッドである。
引数として区切り文字を指定でき、省略した場合はカンマ (,) が使用される。
null や undefined の要素は空文字列として扱われる。
const fruits = ["apple", "banana", "cherry"];
// デフォルトはカンマ区切り
console.log(fruits.join()); // "apple,banana,cherry"
// 区切り文字を指定
console.log(fruits.join(", ")); // "apple, banana, cherry"
console.log(fruits.join(" - "));// "apple - banana - cherry"
console.log(fruits.join("")); // "applebananacherry"
// null / undefinedは空文字列として扱われる
const mixed = ["a", null, "b", undefined, "c"];
console.log(mixed.join("-")); // "a--b--c"
// 数値の配列をCSV形式に変換する例
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.join(",")); // "1,2,3,4,5"
toString() は配列をカンマ区切りの文字列に変換するメソッドである。
引数無しの join() と同等の結果を返す。
const arr = [1, 2, 3];
console.log(arr.toString()); // "1,2,3"
console.log(arr.join(",")); // "1,2,3" (同等の結果)
// 文字列への暗黙的な変換でもtoString()が呼ばれる
console.log("array: " + arr); // "array: 1,2,3"
concat
concat(...values) は、元の配列に引数の配列や値を結合した新しい配列を返す非破壊的なメソッドである。
複数の配列や値を一度に結合することができる。
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
// 2つの配列を結合
const combined = arr1.concat(arr2);
console.log(combined); // [1, 2, 3, 4, 5, 6]
console.log(arr1); // [1, 2, 3] (変更されない)
// 複数の配列を一度に結合
const all = arr1.concat(arr2, arr3);
console.log(all); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 配列と値を混在させて結合
const mixed = arr1.concat(4, 5, arr3);
console.log(mixed); // [1, 2, 3, 4, 5, 7, 8, 9]
スプレッド構文 [...arr1, ...arr2] を使用した場合と比較すると、結果は同等であるが記法が異なる。
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
// concatを使用した場合
const result1 = arr1.concat(arr2);
// スプレッド構文を使用した場合 (同等の結果)
const result2 = [...arr1, ...arr2];
console.log(result1); // [1, 2, 3, 4, 5, 6]
console.log(result2); // [1, 2, 3, 4, 5, 6]
// スプレッド構文は途中に要素を挿入する際に柔軟
const result3 = [...arr1, 99, ...arr2];
console.log(result3); // [1, 2, 3, 99, 4, 5, 6]
関連情報
- JavaScriptの基礎 - 配列の操作
- 配列の作成、要素へのアクセス、追加・削除、検索メソッド
- JavaScriptの基礎 - 配列の反復メソッド
- forEach, map, filter, reduce等の反復処理メソッド
- JavaScriptの基礎 - 文字列
- 文字列の操作メソッドと基本的な使い方