Array.prototype.map()
map()
メソッドは、与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成します。
試してみましょう
構文
// アロー関数
map((element) => { /* … */ })
map((element, index) => { /* … */ })
map((element, index, array) => { /* … */ })
// コールバック関数
map(callbackFn)
map(callbackFn, thisArg)
// インラインコールバック関数
map(function(element) { /* … */ })
map(function(element, index) { /* … */ })
map(function(element, index, array){ /* … */ })
map(function(element, index, array) { /* … */ }, thisArg)
引数
callbackFn
-
arr
の要素ごとに呼び出される関数です。callbackFn
が実行されるたびに、返された値がnewArray
に追加されます。この関数は以下の引数と共に呼び出されます。
element
-
配列内で現在処理中の要素です。
index
-
現在処理中の要素の配列内における添字です。
array
-
map
が呼び出された配列です。
thisArg
省略可-
callbackFn
を実行するときにthis
として使う値です。
返値
与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列です。
解説
map
は、与えられた callbackFn
関数を配列の順番通りに、各要素に対して一度ずつ呼び出し、その結果から新しい配列を生成します。 callbackFn
は、値が代入されている配列の要素に対してのみ呼び出されます (undefined
が代入されているものも含みます)。
これは配列の中で存在しない要素、すなわち以下の要素に対しては呼び出しません。
- 設定されたことがない添字
- 削除された要素
- 値を割り当てられたことがない要素
map() を使用すべきではない場合
引数の詳細
callbackFn
は、要素の値、要素の添字、走査されている Array オブジェクトという 3 つの引数をともなって呼び出されます。
thisArg
引数が与えられた場合は、それがコールバックの this
として使用されます。引数が省略された場合は、 undefined
が this
の値として使用されます。 callbackFn
によって最終的に識別できる this
の値は、関数における通常の this
を決定するルールに従って決まります。
map
は呼び出された配列を変化させません (ただし、呼び出された callbackFn
が配列を変更する可能性はあります)。
map
によって処理される要素の範囲は、最初に callbackFn
が呼び出される前に設定されます。既に訪問した位置に割り当てられている要素や、範囲外の位置に割り当てられている要素に対しては、callbackFn
が呼び出されることはありません。
既存の配列要素が変更されたり、削除された場合、 callbackFn
に渡される値は map
がそれらを訪れた時点での値になり、 map
が削除された要素を訪問することはありません。
警告: 前項で説明したような、参照中の配列の同時進行での変更は(特殊な場合を除いて)普通は避けるべきです。多くの場合、理解しにくいコードになります。
仕様書で定義されているアルゴリズムによって、 map
が呼び出された配列がまばらである場合、結果の配列もまばらとなり、要素を空欄のままにします。
例
数値の配列を平方根の配列にマッピング
次のコードは、数値からなる配列を取り、それらの数値の平方根からなる新しい配列を生成します。
const numbers = [1, 4, 9];
const roots = numbers.map((num) => Math.sqrt(num));
// roots の内容は [1, 2, 3] となる
// numbers の内容は [1, 4, 9] のまま
map を使用して配列内のオブジェクトを再フォーマット
次のコードは、オブジェクトの配列を受け取り、新たにフォーマットされた新しい配列を生成しています。
const kvArray = [
{ key: 1, value: 10 },
{ key: 2, value: 20 },
{ key: 3, value: 30 },
];
const reformattedArray = kvArray.map(({ key, value}) => ({ [key]: value }));
// フォーマットされた配列の内容は [{1: 10}, {2: 20}, {3: 30}]となる
// kvArray は以下のまま
// [{key: 1, value: 10},
// {key: 2, value: 20},
// {key: 3, value: 30}]
引数を含む関数を使用して数値配列を対応付ける
次のコードは、1 つの引数を必要とする関数を使用するときに map
がどのように動作するかを示しています。引数は元の配列を通した map
ループとして、配列の各要素に自動的に割り当てられます。
const numbers = [1, 4, 9];
const doubles = numbers.map((num) => num * 2);
// doubles is now [2, 8, 18]
// numbers is still [1, 4, 9]
汎用的な map の使用
次の例は、 String
に対して map を使用することで、文字列内の文字を UTF-16 コードユニットので表す数値の配列を取得します。
const map = Array.prototype.map;
const charCodes = map.call('Hello World', (x) => x.charCodeAt(0));
// a の内容は [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100] となる
汎用的な map の使用: querySelectorAll
この例では、 querySelectorAll
によって収集されたオブジェクトのコレクションを反復処理する方法を示します。これは querySelectorAll
が NodeList
(オブジェクトの集合)を返すためです。
この場合、画面に選択されているすべての option
の値を返します。
const elems = document.querySelectorAll('select option:checked');
const values = Array.prototype.map.call(elems, ({ value }) => value);
もっと簡単な方法は Array.from()
メソッドを使用することです。
トリッキーな使用例
コールバック関数は第一引数(変換するべき要素)だけを意識して指定するケースがほとんどだと思います。しかし一般的に第一引数しか使わないような関数でも、実は追加のオプション引数を受け取っている場合があります。これは混乱につながる可能性があります。
まずこの例をご覧ください。
['1', '2', '3'].map(parseInt);
返値は [1, 2, 3]
となりそうですが、実際には [1, NaN, NaN]
となります。
parseInt
は大抵一つの引数のみで使われますが、実際には 2 つの引数を取っています。 1 つ目は数値文字列、 2 つ目は基数です。 Array.prototype.map
はコールバックに次の 3 つの引数を与えています。
- その要素
- その添字
- その配列
parseInt
は 3 つ目の引数を無視しますが、 2 つ目の引数は無視しません。これは混乱を起こす可能性があるソースです。
繰り返し手順の正確な例は以下の通りです。
// parseInt(string, radix) -> map(parseInt(value, index))
/* first iteration (index is 0): */ parseInt("1", 0); // 1
/* second iteration (index is 1): */ parseInt("2", 1); // NaN
/* third iteration (index is 2): */ parseInt("3", 2); // NaN
解決策を考えてみましょう。
const returnInt = (element) => parseInt(element, 10);
['1', '2', '3'].map(returnInt); // [1, 2, 3]
// 期待した通り、数値の配列が返る。
// アロー関数構文を使って、より簡潔に上記と同じ結果を得ることが出来ます。
['1', '2', '3'].map((str) => parseInt(str)); // [1, 2, 3]
// ちなみにこの命題ではもっと簡単に同じ結果を得る方法があります。
['1', '2', '3'].map(Number); // [1, 2, 3]
// parseInt() とは違って、 Number() は float または (解決した) 指数表現を返します。
['1.1', '2.2e2', '3e300'].map(Number); // [1.1, 220, 3e+300]
// 比較のために、上記の配列に parseInt() を用いると次のようになります。
['1.1', '2.2e2', '3e300'].map((str) => parseInt(str)); // [1, 2, 3]
parseInt
を引数として呼び出された map メソッドの代替出力の 1 つは、次のように実行されます。
const strings = ['10', '10', '10'];
const numbers = strings.map(parseInt);
console.log(numbers);
// 実際の結果 [10, NaN, 2] は上記の説明からすると意外なものかもしれません。
undefined を持つ対応付けられた配列
undefined
または nothing を返すと、以下のものを返します。
const numbers = [1, 2, 3, 4];
const filteredNumbers = numbers.map((num, index) => {
if (index < 3) {
return num;
}
})
// index は 0 から始まるので、 filterNumbers は 1,2,3 および undefined になります。
// filteredNumbers は [1, 2, undefined, undefined]
// numbers は [1, 2, 3, 4] のまま
仕様書
Specification |
---|
ECMAScript Language Specification # sec-array.prototype.map |
ブラウザーの互換性
BCD tables only load in the browser