markdown

はじめに (対象読者・この記事でわかること)

本記事は、JavaScript の基礎は理解しているが配列操作の中でも特に Array.map の正しい使い方やベストプラクティスに悩んでいるエンジニアを対象としています。
map が何をするのか、普通に書くだけでは性能にどのような影響が出るのか、そして実務で頻出する「データ変換」や「非同期処理」と組み合わせた高度なテクニックまでを体系的に学べます。
この記事を読むことで、map の基本的なシンタックスから、ES6+ の機能を活用したコードの可読性向上、さらにパフォーマンスのボトルネックを回避する方法まで実装できるようになります。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。
- JavaScript の基本文法(変数宣言、関数、オブジェクトリテラル)
- ES6 以降の構文(アロー関数、スプレッド構文、テンプレートリテラル)
- Node.js もしくはブラウザ上でのデバッグ方法

map の基本と活用シーン

Array.map は、配列の各要素に対してコールバック関数を適用し、結果を新しい配列として返す高階関数です。

Js
const numbers = [1, 2, 3]; const doubled = numbers.map(n => n * 2); // [2, 4, 6]

なぜ map が必要か
- 不変性: 元の配列を変更せずに新しい配列を生成できるため、状態管理が楽になる
- 関数型スタイル: 副作用が少なく、テストしやすいコードが書ける
- 可読性: for 文と比べて意図が一目で分かりやすい

しかし、誤用するとパフォーマンス低下意図しない副作用 が起こることがあります。たとえば、map のコールバック内部で重い計算や I/O を行うと、配列長に比例して処理時間が膨らみます。また、map の結果を使わずに副作用だけで済ませるケースは forEachreduce の方が適切です。
本節では、map がどのように内部で走るか(Array の内部実装、最適化されるケース)と、実務でよくある「配列のオブジェクト変換」や「ネストされた配列のフラット化」シナリオを例に、正しい設計指針 を示します。

実践!Array.map を使いこなす

ステップ1:シンプルなデータ変換

まずは、配列のオブジェクトから特定のプロパティだけを抽出する典型的な例です。

Js
const users = [ { id: 1, name: 'Alice', age: 28 }, { id: 2, name: 'Bob', age: 34 }, { id: 3, name: 'Carol', age: 23 } ]; // name のみを抽出 const names = users.map(user => user.name); // => ['Alice', 'Bob', 'Carol']

ポイントは アロー関数で簡潔に書く ことと、不要な中括弧と return を省く ことです。これだけでコードの可読性が大幅に向上します。

ステップ2:オブジェクトの再構築とデフォルト値

実務では、取得したデータに欠損があるケースが多いです。map とスプレッド構文、デフォルト引数を組み合わせると、欠損フィールドに自動的にデフォルトを埋め込めます。

Js
const raw = [ { id: 1, name: 'Alice' }, { id: 2, age: 34 }, { id: 3, name: 'Carol', age: 23 } ]; const normalized = raw.map(item => ({ id: item.id, name: item.name ?? '匿名', age: item.age ?? 0 })); /* [ { id: 1, name: 'Alice', age: 0 }, { id: 2, name: '匿名', age: 34 }, { id: 3, name: 'Carol', age: 23 } ] */

ここでのコツは オブジェクトリテラルを丸括弧で囲む(構文エラー回避)と、Nullish Coalescing (??) を使うことで nullundefined のみを対象にデフォルトを設定できる点です。

ステップ3:非同期処理との組み合わせ(Promise.all と map)

map 自体は同期関数ですが、非同期処理の配列を同時に走らせたいときは Promise.all と組み合わせます。

Js
const urls = [ 'https://api.example.com/a', 'https://api.example.com/b', 'https://api.example.com/c' ]; async function fetchAll() { const responses = await Promise.all(urls.map(url => fetch(url))); const jsonData = await Promise.all(responses.map(r => r.json())); return jsonData; }

重要な点は、エラーハンドリング を忘れずに行うことです。Promise.all はどれか一つでも reject すると全体が reject になるため、部分的に失敗させても処理を続行したい場合は Promise.allSettled を使用します。

ハマった点やエラー解決

1. map の戻り値を無視してしまう

Js
arr.map(item => console.log(item)); // ← 目的は副作用だけ

map は新しい配列を返すので、結果が不要なら forEachfor...of に置き換えるべきです。これにより意図しないメモリ使用量を抑えられます。

2. 配列が巨大になるとメモリが足りなくなる

数十万以上の要素を一括で map すると、内部で新しい配列を生成するため ヒープサイズが急上昇 します。対策は バッチ処理(スライスして分割)や ストリームライブラリhighland, stream.Transform)の活用です。

Js
function chunkedMap(array, chunkSize, fn) { const result = []; for (let i = 0; i < array.length; i += chunkSize) { const chunk = array.slice(i, i + chunkSize); result.push(...chunk.map(fn)); } return result; }

3. this の参照ミス

古いコードで function を使うと、this が期待通りにバインドされずエラーになるケースがあります。ES6 のアロー関数は レキシカルスコープthis を継承するので、常にアロー関数に置き換えることを推奨します。

Js
class Foo { constructor(arr) { this.arr = arr; } bad() { // this が undefined になる return this.arr.map(function(item) { return this.toString(item); }); } good() { // アロー関数で this が正しく参照できる return this.arr.map(item => this.toString(item)); } }

解決策まとめ

問題 解決策
map の副作用だけの利用 forEach へ置き換える
大規模配列でメモリ逼迫 バッチ処理 chunkedMap かストリーム利用
this バインドエラー アロー関数に統一、bind で明示的に束縛
非同期エラーで全体が失敗 Promise.allSettled で個別結果を取得

まとめ

本記事では、Array.map の基本から実務で役立つ応用テクニック、さらにパフォーマンスやエラーハンドリングに関する注意点 を体系的に解説しました。

  • シンプルな変換:アロー関数とスプレッド構文で可読性向上
  • 欠損データのデフォルト埋め:Nullish Coalescing と丸括弧で安全実装
  • 非同期処理Promise.all / Promise.allSettled と組み合わせた同時実行

この記事を読むことで、配列操作のパフォーマンスを最適化しつつ、バグの温床になりやすいパターンを回避できるようになります。次回は、Array.reduceArray.filter を組み合わせた高度データ集計パターンについて解説する予定です。

参考資料