アロー関数は何度か調べているのに、function、const、=>が一緒に出てくると、また少し分からなくなります。特にhealAmount => currentHp => ...のように矢印が続くコードは、「引数はどれか」「なぜ関数を返すのか」が長年しっくりきませんでした。
今回は、アロー関数の省略記法だけでなく、引数の丸括弧、returnの省略、関数を返して2段階に分ける意味まで整理してみました。何回も調べ直している今の理解を、あとから見返せるように残しておこうと思います。
まだ途中段階なので、勘違いなどあれば教えていただけるとうれしいです。
今日やったこと
通常の関数、関数式、アロー関数を並べ、引数の書き方を確認しました。
JavaScriptでは、同じように関数を作れても、主に次の3つの書き方があります。どの書き方でも、関数名()と書けば処理を実行できます。 typescriptbook
// 1. 関数宣言:sayHello という名前の関数を直接作る
function sayHello() {
console.log('こんにちは');
}
// 2. 関数式:sayHelloWithFunction という定数に関数を入れる
const sayHelloWithFunction = function () {
console.log('こんにちは');
};
// 3. アロー関数:sayHelloWithArrow という定数にアロー関数を入れる
const sayHelloWithArrow = () => {
console.log('こんにちは');
};
// どの書き方でも、関数名の後ろに () を付けて実行する
sayHello();
sayHelloWithFunction();
sayHelloWithArrow();
-
function sayHello() {}は、名前付きの関数を直接定義する書き方 -
const sayHello = function () {}は、関数をconstの変数へ入れる書き方 -
const sayHello = () => {}は、アロー関数をconstの変数へ入れる書き方 -
関数名は関数そのもの、関数名()は関数を実行した結果
関数は、ゲーム内で「回復する」「メニューを開く」といった処理のまとまりに名前を付ける感覚に少し近いかもしれません。関数名はコマンドそのもの、()を付けた関数名はコマンドを実行した結果、と考えると整理しやすそうです。
引数と丸括弧のルール
アロー関数では、矢印の左側に「関数が受け取る引数」を書きます。
| 引数の数 | 書き方 | 意味 |
|---|---|---|
| なし | () => {} |
何も受け取らない |
| 1つ |
value => {} または (value) => {}
|
valueを受け取る |
| 2つ以上 | (value1, value2) => {} |
複数の値を受け取る |
// 引数なし:() は「受け取るものがない」という意味
const showMenu = () => {
console.log('メニューを開く');
};
// 引数1つ:() は付けても、省略しても同じ意味
const healWithParentheses = (currentHp) => {
return currentHp + 30;
};
const healWithoutParentheses = currentHp => {
return currentHp + 30;
};
// 引数2つ:() の中にカンマ区切りで書く
const add = (number1, number2) => {
return number1 + number2;
};
引数が1つだけの場合、丸括弧は省略できます。ただし、付けても問題ありません。 typescriptbook
![]() |
「引数が1つなら丸括弧は“省略できる”だけで、最初から省略しなければいけないわけではないんだね。」 |
今の自分には、(currentHp) => {}のように丸括弧を残しておくほうが、「ここが引数」と分かりやすいと感じました。
つまずいたところ
const、returnの省略、関数を返して2段階に分ける理由が混ざっていました。
constで固定されるもの
JavaScriptでは、const function sayHello() {}とは書きません。constは関数に付けるものではなく、変数を宣言するキーワードです。
// JavaScriptではこの書き方はできない
// const function sayHello() {
// console.log('こんにちは');
// };
constを使いながら通常のfunctionを書く場合は、関数を変数へ入れる形になります。
// sayHello に function で作った関数を入れる
const sayHello = function () {
console.log('こんにちは');
};
// sayHello に入っている関数を実行する
sayHello();
constで宣言した変数には、別の値を再代入できません。つまり、sayHelloに入っている関数を途中で別の関数へ入れ替えない意図を表せます。 developer.mozilla
ゲームの回復コマンドで考えると、少し整理しやすくなりました。
// 現在HPを受け取り、30回復した後のHPを返す関数
// 最大HPの100を超えないようにしている
const heal = (currentHp) => {
return Math.min(currentHp + 30, 100);
};
// heal に入っている処理は同じでも、渡す現在HPによって結果は変わる
console.log(heal(20)); // 50
console.log(heal(80)); // 100
constで固定されるのは、healという名前に登録した「回復する処理」です。一方で、関数の計算結果まで固定されるわけではありません。現在HPが20なら回復後は50、現在HPが80なら最大HPを超えないように100になります。
本来あるreturnが省略されている
アロー関数で分かりにくくなる大きな理由は、returnが省略できることだと思いました。
// {} を使うため、return を明示して計算結果を返す
const addWithReturn = (number) => {
return number + 1;
};
// 右側が1つの式だけなので、{} と return を省略できる
const addShort = number => number + 1;
console.log(addWithReturn(5)); // 6
console.log(addShort(5)); // 6
厳密には「1行だから」ではなく、=>の右側が1つの式だけのとき、{}とreturnを省略できます。多くの場合に1行で書かれるため、「1行なら省略できる」と覚えると最初は理解しやすそうです。 developer.mozilla
今の理解(メモ)
関数を返す形は、「先に決める設定」と「後で分かる値」を分けたいときに意味がありそうです。
例えば、回復量を先に決めてから、戦闘中に現在HPへ使うスキルを考えてみます。
// 回復量を受け取り、回復処理そのものを返す関数
function createHealSkill(healAmount) {
// 返すのは、現在HPを受け取る別の関数
return function (currentHp) {
// 最大HPの100を超えない回復後HPを返す
return Math.min(currentHp + healAmount, 100);
};
}
このコードでは、外側のcreateHealSkillが「回復量を設定したスキル」を作ります。内側の関数は、後で現在HPを受け取り、回復後のHPを計算します。
// 30回復する小回復スキルを作っておく
const smallHeal = createHealSkill(30);
// 60回復する大回復スキルを作っておく
const largeHeal = createHealSkill(60);
// 実際にスキルを使うとき、現在HPを渡す
console.log(smallHeal(20)); // 50
console.log(largeHeal(20)); // 80
console.log(smallHeal(90)); // 100
2段階に分ける意味は、次のように整理できそうです。
-
createHealSkill(30):回復量30のスキルを作る -
smallHeal:作った小回復スキルを持っておく -
smallHeal(20):現在HP20へ小回復スキルを使う
ゲームで考えると、「スキルの回復量を設定するタイミング」と、「戦闘中に現在HPへ使うタイミング」が別になっています。先に小回復と大回復を用意しておけば、戦闘中は現在HPだけを渡して使えます。
![]() |
「“関数を返すために分ける”のではなくて、先に決めたい設定と、後で分かる現在HPを分けるために関数も2段階になるんだね。」 |
関数を返す関数は、引数として受け取った設定を使える状態で、新しい関数を返せます。このように関数を引数に取ったり、関数を返したりする関数は、高階関数と呼ばれます。 itsakura
アロー関数にすると、returnが見えなくなる
上のcreateHealSkillをアロー関数に書き換えると、まずは次の形になります。
// 外側の関数は、回復量を受け取る
const createHealSkill = (healAmount) => {
// return で、現在HPを受け取る内側の関数を返す
return (currentHp) => {
// 内側の関数は、回復後のHPを返す
return Math.min(currentHp + healAmount, 100);
};
};
この形なら、returnが2つあり、外側が関数を返し、内側が数字を返すことが見えます。
- 外側の
return:(currentHp) => { ... }という関数を返す - 内側の
return:回復後HPという数字を返す
さらに短く書くと、2つのreturnが省略されます。
// 外側は内側の関数を返し、内側は回復後HPを返す
const createHealSkill = healAmount =>
currentHp => Math.min(currentHp + healAmount, 100);
この短いコードを読めるようになるには、頭の中で次の形へ戻すのがよさそうです。
const createHealSkill = (healAmount) => {
return (currentHp) => {
return Math.min(currentHp + healAmount, 100);
};
};
短いアロー関数を見てすぐ理解できる人は、矢印を魔法の記号として読んでいるわけではなく、省略されたreturnと関数の形を頭の中で補っているのだと思います。今の自分は、省略前の形へ戻して読むほうが理解しやすいです。
二重のmapは別の話
array.map(x => x.map(y => ...))も矢印が続いて見えますが、これは「関数を返して後で実行する」形とは別です。map()は、配列の各要素へ関数を適用し、新しい配列を作るメソッドです。MDNのmap()解説
const itemGroups = [
['やくそう', 'どくけし'],
['ポーション', 'エリクサー'],
];
// 外側の map は、アイテムのグループを1つずつ受け取る
const displayGroups = itemGroups.map((itemGroup) => {
// 内側の map は、グループ内のアイテムを1つずつ受け取る
return itemGroup.map((itemName) => {
// アイテム名を表示用の文字列に変える
return `持ち物: ${itemName}`;
});
});
console.log(displayGroups);
この場合、外側のmapが返しているのは、内側のmapが作った配列です。createHealSkillのように、後で実行するための関数を返しているわけではありません。
Vueでも、配列を画面表示向けのデータへ変える場面でアロー関数を使えそうです。
<script setup>
// 持ち物の名前を配列で用意する
const itemNames = ['やくそう', 'ポーション', 'エリクサー'];
// アイテム名を受け取り、画面表示用の文字列を返す
const createDisplayName = (itemName) => {
return `持ち物: ${itemName}`;
};
// map で各アイテム名を表示用の文字列に変換する
const displayNames = itemNames.map((itemName) => {
return createDisplayName(itemName);
});
</script>
<template>
<!-- displayNames の数だけ、リスト項目を表示する -->
<ul>
<li v-for="displayName in displayNames" :key="displayName">
{{ displayName }}
</li>
</ul>
</template>
この例では、mapの中に処理を全部書かず、createDisplayNameへ分けています。持ち物一覧の各アイテムを表示用に整える流れとして、画面で何が起きているか追いやすくなりそうです。v-forによる一覧表示は、Vue.js公式ドキュメントも見返したいです。
次にやりたいこと
省略されたreturnと引数の受け渡しを、実際に確かめたいです。
-
createHealSkillを通常の関数、アロー関数、短いアロー関数の3種類で書き、結果を比べる -
smallHealとlargeHealを作り、同じ現在HPへ使ったときの結果を確認する -
typeof createHealSkill(30)をコンソールへ出し、戻り値が関数であることを確認する - 二重の
mapで、外側と内側のmapが返す値をログに出す - Vueの
v-forで持ち物一覧を表示し、mapで作った表示用データを渡してみる
短いアロー関数で分からなくなったときは、「本来ならどこにreturnがあるか」と、「矢印の左側はどの関数の引数か」を確認してみたいです。
今日の整理
今回出てきた関数と戻り値の関係をまとめました。
| 書き方・実行 | 引数 | 戻り値 | 役割 |
|---|---|---|---|
heal(currentHp) |
currentHp |
回復後HPの数字 | 現在HPから回復後HPを計算する |
createHealSkill(healAmount) |
healAmount |
回復用の関数 | 回復量を設定したスキルを作る |
smallHeal(currentHp) |
currentHp |
回復後HPの数字 | 設定済みのスキルを使う |
healAmount => currentHp => ... |
外側と内側に1つずつ | 関数、次に数字 |
returnを省略した形 |
itemGroup.map(...) |
itemGroup |
表示用文字列の配列 | 配列を変換して返す |
おわりに
今回一番しっくりきたのは、関数を返すために無理に2段階へ分けるのではなく、先に決める設定と、後で分かる値を分けたいときに関数も2段階になる、という考え方です。小回復スキルを作る例では、回復量を決めるときと、戦闘中の現在HPに使うときが別なので、関数を返す意味が少し見えました。
また、短いアロー関数ではreturnや引数の丸括弧が省略されるため、慣れていないうちは省略前の形へ戻して読むほうがよさそうです。何度も調べ直しながらでも、関数が何を受け取り、何を返すかを自分の言葉で説明できるようになりたいと思います。
学習中の内容なので、勘違いや理解不足もあると思います。もし気になる点があれば教えてもらえるとうれしいです。
注意事項
本ブログに掲載している内容は、私個人の見解であり、
所属する組織の立場や戦略、意見を代表するものではありません。
あくまでエンジニアとしての経験や考えを発信していますのでご了承ください。
