JavaScriptの配列には、さまざまな便利なメソッドが用意されています。
その中でもmap()は、配列の各要素を別の値へ変換し、その結果から新しい配列を作るときによく使われるメソッドです。
この記事では、map()の基本的な使い方から、応用的な使い方、注意点まで説明します。
map()の概要
map()は、配列の各要素に対してコールバック関数を実行し、その戻り値から新しい配列を作るメソッドです。
基本的な構文は次のとおりです。
const result = array.map(element => {
// 処理
});
コールバック関数には、処理対象の要素に加えて、インデックスや元の配列も引数として受け取れます。
const result = array.map((element, index, array) => {
// 処理
});
| 引数 | 内容 |
|---|---|
element |
現在処理している要素 |
index |
現在処理している要素のインデックス |
array |
map()を呼び出した元の配列 |
map()の基本的な特徴
map()には、次のような特徴があります。
- 配列の各要素に対して処理を行う
- コールバック関数の戻り値が新しい配列の要素になる
- 元の配列とは別の新しい配列を返す
- 元の配列と同じ長さの配列を返す
基本的な使い方
プリミティブ型の値を変換する
最も基本的な使い方として、プリミティブ型の値を変換する例を見ていきます。
// number
const numbers = [1, 2, 3, 4, 5];
// 各要素を2倍に変換。
const result = numbers.map(n => n * 2);
console.log(result);
// [2, 4, 6, 8, 10]
// string
const words = ["apple", "banana", "orange"];
// 小文字から大文字へ変換。
const result = words.map(word => word.toUpperCase());
console.log(result);
// ["APPLE", "BANANA", "ORANGE"]
オブジェクトの配列を変換する
オブジェクトの配列を扱う場合も見てみましょう。
const users = [
{ name: "田中", age: 25 },
{ name: "佐藤", age: 30 },
{ name: "鈴木", age: 20 }
];
// usersからnameだけ取り出す。
const names = users.map(user => user.name);
console.log(names);
// ["田中", "佐藤", "鈴木"]
応用的な使い方
オブジェクトを別の形に変換する
map()では、オブジェクトを別の形に変換することもできます。
const users = [
{ name: "田中", age: 25 },
{ name: "佐藤", age: 30 }
];
const result = users.map(user => ({
name: user.name,
isAdult: user.age >= 20
}));
console.log(result);
// [
// { name: "田中", isAdult: true },
// { name: "佐藤", isAdult: true }
// ]
filter()と組み合わせる
map()とfilter()を組み合わせることもできます。
例えば、20歳以上のユーザーの名前だけを取得する場合です。
const users = [
{ name: "田中", age: 25 },
{ name: "佐藤", age: 17 },
{ name: "鈴木", age: 30 }
];
const names = users
.filter(user => user.age >= 20)
.map(user => user.name);
console.log(names);
// ["田中", "鈴木"]
この処理は、
users
↓
filter()
20歳以上だけ残す
↓
map()
名前だけ取り出す
↓
["田中", "鈴木"]
という流れになっています。
filter()で対象を絞り込み、その後map()で必要な形に変換しています。
map()とreduce()を組み合わせる
map()で各要素を変換した後、その結果をreduce()で1つの値にまとめることもできます。
例えば、商品の価格に消費税を加えた後、その合計金額を求める場合です。
const prices = [100, 200, 300];
const total = prices
.map(price => price * 1.1)
.reduce((sum, price) => sum + price, 0);
console.log(total);
// 660
このように、配列に対する処理を組み合わせることで、複数の処理を順番に行うことができます。
注意点
元の配列を直接変更しない
map()は新しい配列を返すため、通常は元の配列を変更しません。
const numbers = [1, 2, 3];
const result = numbers.map(n => n * 2);
console.log(numbers);
// [1, 2, 3]
console.log(result);
// [2, 4, 6]
ただし、コールバック関数の中で元の配列を変更すれば、元の配列も変更されます。
const numbers = [1, 2, 3];
const result = numbers.map((n, index, array) => {
array[index] = n * 2;
return n * 2;
});
console.log(numbers);
// [2, 4, 6]
map()の中で元の配列を変更する処理は、コードを分かりにくくするため、基本的には避けた方がよいでしょう。
また、map()の処理中に配列へ要素を追加しても、その追加分は処理されません。
const numbers = [1, 2, 3];
const result = numbers.map((n, index, array) => {
if (index === 0) {
array.push(4);
}
return n * 2;
});
console.log(result);
// [2, 4, 6]
console.log(numbers);
// [1, 2, 3, 4]
この場合、4は配列に追加されていますが、map()では処理されません。
このように、map()の処理中に元の配列を変更すると、意図しない動作につながる可能性があります。
returnの書き忘れに注意する
コールバック関数をブロック形式で書く場合、returnを忘れるとundefinedが返ります。
const numbers = [1, 2, 3];
const result = numbers.map(n => {
n * 2;
});
console.log(result);
// [undefined, undefined, undefined]
正しくは次のようにします。
const result = numbers.map(n => {
return n * 2;
});
または、式をそのまま返す場合は次のように書けます。
const result = numbers.map(n => n * 2);
アロー関数でオブジェクトを返す場合
アロー関数でオブジェクトを返す場合は、オブジェクトリテラルを()で囲みます。
例えば、次のように書きます。
const users = [
{ name: "田中", age: 25 },
{ name: "佐藤", age: 30 }
];
const result = users.map(user => ({
name: user.name,
age: user.age
}));
console.log(result);
// [
// { name: "田中", age: 25 },
// { name: "佐藤", age: 30 }
// ]
()で囲むことで、{}をアロー関数のブロックではなく、オブジェクトとして扱います。
returnを使う場合は、次のようにも書けます。
const result = users.map(user => {
return {
name: user.name,
age: user.age
};
});
オブジェクトの参照に注意する
map()によって新しい配列は作られますが、配列の要素がオブジェクトの場合、オブジェクトそのものまでコピーされるわけではありません。
const users = [
{ name: "田中", age: 20 }
];
const result = users.map(user => user);
result[0].age = 30;
console.log(users[0].age);
// 30
users[0]とresult[0]は同じオブジェクトを参照しているためです。
オブジェクト自体を新しく作りたい場合は、スプレッド構文などを利用します。
const result = users.map(user => ({
...user
}));
ただし、スプレッド構文によるコピーもシャローコピーであり、ネストしたオブジェクトまで完全にコピーするわけではない点に注意する必要があります。
map()とfilter()を使い分ける
map()は要素を削除するためのメソッドではありません。
例えば、偶数だけを残そうとして次のように書いても、
const numbers = [1, 2, 3, 4];
const result = numbers.map(n => {
if (n % 2 === 0) {
return n;
}
});
console.log(result);
// [undefined, 2, undefined, 4]
要素が削除されるのではなく、undefinedが入ります。
条件に合う要素だけを残したい場合は、filter()を使います。
const result = numbers.filter(n => n % 2 === 0);
console.log(result);
// [2, 4]
map()とforEach()を使い分ける
map()とforEach()はどちらも配列の各要素に対して処理を行いますが、目的が異なります。
map()は、変換した結果を新しい配列として利用する場合に適しています。
const numbers = [1, 2, 3];
const result = numbers.map(n => n * 2);
console.log(result);
// [2, 4, 6]
一方、単に各要素に対して処理を実行したい場合はforEach()が適しています。
numbers.forEach(n => {
console.log(n);
});
配列を変換する必要があるのか、それとも単に処理を実行したいだけなのかを考えて、適切なメソッドを選択することが大切です。
配列に空の要素がある場合
配列の途中に空の要素がある場合、その要素についてはコールバック関数が実行されません。
const numbers = [1, , 3];
const result = numbers.map(n => n * 2);
console.log(result);
// [2, empty, 6]
この場合、空の要素は処理されず、そのまま空の要素として残ります。
配列に空の要素が存在する場合は、この点に注意する必要があります。