作成者別アーカイブ: 池上龍一

【JavaScript】配列をfor文で回すのはもうやめよう。配列の高階関数なメソッドを使いこなす

for文(for…ofを前提として書きますが)では、「配列(かなにかiterableなもの)を1件ずつ処理している」以上の情報は、その内容を読み込まないと得られません。

Array.prototypeに存在する高階関数なメソッドを使用すると、ざっくりと「新しい配列を作っているんだな」とか「集計してるんだな」とかのある程度の処理内容を読み手に伝えることができます

その上、目的のデータ形式を得るための書き方が決まっていて、必然的にconstを使いやすくなるというメリットもあります。

高階関数?

高階関数とは、関数を引数に取る関数のことです。

JavaScriptは、関数が第一級オブジェクトな言語なので、関数を変数に格納したり、引数として渡したり、関数の結果として返したりすることができます。

配列の高階関数なメソッドたち

配列のメソッドには、この高階関数の形をとって、

  • 配列の要素ごとに
  • 引数として渡した関数の処理を適用して
  • そのメソッドに応じた結果を返す

という挙動をするものがあります。それらしい名前がついているので、なんとなく何が行われているかわかります。

以下のメソッドを紹介します。

  • .map()
  • .filter()
  • .find()
  • .some()
  • .every()
  • .reduce()

また、以下の例ではオブジェクトの分割代入(MDN)を使用します。

免責

細かい使い方はMDNをご覧ください。

新しい配列を作る .map()

Array.prototype.map() – JavaScript | MDN

配列の要素ごとに、引数として渡した関数の処理を適用して、その関数の返り値を要素とした新しい配列を返します。

数列の数字をそれぞれ2倍にする

データ

const nums = [1, 2, 3,];

.map()

const twicedNums = nums.map(n => n * 2);
console.log(twicedNums); // [ 2, 4, 6 ]

for...of

const twicedNums = [];
for (const n of nums) {
  twicedNums.push(n * 2);
}
console.log(twicedNums); // [ 2, 4, 6 ]

条件に応じて要素を抽出する .filter()

Array.prototype.filter() – JavaScript | MDN

配列の要素ごとに、引数として渡した関数の処理を実施して、その関数の返り値がtruthyだった値を要素とした新しい配列を返します。

数列から奇数の数字だけを抽出する

データ

const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9,];

.filter()

const odds = nums.filter(n => n % 2); // %は剰余。0はfalsy, 1はtruthy
console.log(odds); // [ 1, 3, 5, 7, 9]

for...of

const odds = [];
for (const n of nums) {
    if (n % 2) odds.push(n)
}
console.log(odds); // [ 1, 3, 5, 7, 9]

条件に合致した要素を返す .find()

Array.prototype.find() – JavaScript | MDN

配列の要素ごとに、引数として渡した関数の処理を実施して、その関数の返り値が最初にtruthyになった要素を返します。

自分の手持ちから、初めて捕まえたポケモン(idが0)を探す

データ

const myParty = [
  {id: 1, name: "Dragonite"},
  {id: 3, name: "Pidgeotto"},
  {id: 0, name: "Pikachu"},
  {id: 2, name: "Slowbro"},
];

.find()

const myFirstPokemon = myParty.find(({id}) => id === 0);
console.log(myFirstPokemon); // { id: 0, name: 'Pikachu' }

for...of

let myFirstPokemon;
for (const p of myParty) {
  if (p.id === 0) {
    myFirstPokemon = p;
    break;
  }
}
console.log(myFirstPokemon); // { id: 0, name: 'Pikachu' }

論理和を返す .some()

Array.prototype.some() – JavaScript | MDN

配列の要素ごとに、引数として渡した関数の処理を実施して、その関数の返り値がひとつでもtruthyになればtrueを返します。

自分の好きなキャラクターの中にポケモンが含まれるかどうか調べる

データ

const myFavoriteCharacters = [
  {
    name: "Fukuda",
    source: "Girls und Panzer"
  },
  {
    name: "Swadloon",
    source: "Pokemon"
  },
  {
    name: "Kairu",
    source: "Microsoft"
  },
];

some()

const existsPokemon = myFavoriteCharacters.some(({source}) => source === "Pokemon");
console.log(existsPokemon); // true

for...of

let existsPokemon = false;
for (const {source} of myFavoriteCharacters) {
  if (source === "Pokemon") {
    existsPokemon = true;
    break;
  }
}
console.log(existsPokemon); // true

論理積を返す .every()

Array.prototype.every() – JavaScript | MDN

配列の要素ごとに、引数として渡した関数の処理を実施して、その関数の返り値がすべてtruthyになればtrueを返します。

自分の好きなキャラクターがポケモンだけかどうか調べる

データ

const myFavoriteCharacters = [
  {
    name: "Fukuda",
    source: "Girls und Panzer"
  },
  {
    name: "Swadloon",
    source: "Pokemon"
  },
  {
    name: "Kairu",
    source: "Microsoft"
  },
];

every()

const isOnlyPokemon = myFavoriteCharacters.every(({source}) => source === "Pokemon");
console.log(isOnlyPokemon); // false

for...of

let isOnlyPokemon = true;
for (const {source} of myFavoriteCharacters) {
  if (source !== "Pokemon") {
    isOnlyPokemon = false;
    break;
  }
}
console.log(isOnlyPokemon); // false

処理結果を積み重ねていく .reduce()

Array.prototype.reduce() – JavaScript | MDN

配列の要素ごとに、引数として渡した関数の処理を実施して、その関数の返り値をひとつの変数に集積した結果を返します。

これだけ少し特殊です。関数の書き方によって何でもできます。今まで出てきたメソッドなんかも実装できます。

reduceは「減らす」。配列の要素をひとつずつ削ってマージしていくイメージでしょうか(実際にもとの配列を破壊することはありませんが)。

自分の毎月の光熱費をすべて合計する

データ

const myUtilityExpensesList = [
  { month: "Jan", fuel: 5000, light: 3000, water: 2500 },
  { month: "Feb", fuel: 4600, light: 3200, water: 3000 },
  { month: "Mar", fuel: 5200, light: 4000, water: 2800 },
];

.reduce()

  • .reduce()の第1引数にコールバック関数、第2引数に結果の初期値(後述)を渡します。
  • 第1引数に渡したコールバック関数の第1引数に最終結果となる累積値、第2引数に配列の各要素を渡します。
  • 関数内でreturnされた値が、つぎの要素での累積値として扱われます。
    • そのため、1要素目の実行時には累積値が存在しないので、これを.reduce()の第2引数で代替します。
const totalMyUtilityExpenses = myUtilityExpensesList.reduce(
  // 第1引数が累積値、第2引数が配列の要素
  (result, item) => result + item.fuel + item.light + item.water,
  0
);
console.log(totalMyUtilityExpenses); // 33000

for...of

let totalMyUtilityExpenses = 0;
for (const item of myUtilityExpensesList) {
  totalMyUtilityExpenses = totalMyUtilityExpenses + item.fuel + item.light + item.water
}
console.log(totalMyUtilityExpenses); // 33000

これについてはいろんな例を見たほうがよいので、MDNも読んでみてください。

関数のパイプなんかもできます。

// 任意の文字の次の文字を標準出力に出す
[str => str.codePointAt(0) + 1, String.fromCharCode, console.log].reduce((data, func) => func(data), "A"); // 'B'

おわり

楽しい配列ライフを!