JavaScript超入門⑤ 関数で処理をまとめよう
function、引数、戻り値、アロー関数、変数のスコープを学びます。
似たコードを何度もコピーしていると、途中で直したい箇所が出たときに困ります。「同じ処理を、違う値で使う」ための仕組みが関数です。
今回はfunctionから始め、引数、戻り値、アロー関数、スコープを順に扱います。特に「表示したのに結果を受け取れない」というつまずきを、短い例で整理しましょう。
コード欄は、例ごとに別のプログラムとして試してください。
1. 関数を作ることと、呼ぶことは別
function greet() {
console.log("こんにちは");
}
console.log("開始");
greet();
greet();
console.log("終了");
開始
こんにちは
こんにちは
終了
function greet() { ... }は、greetという関数の定義です。名前とかっこを書いたgreet();が呼び出しになります。
定義しただけでは「こんにちは」は出ません。呼ばれたときに中の処理を行い、終わったら呼び出し元の次へ戻ります。
「関数名を見たら、中身をその場で使う」と考えると、ファイルの上下関係だけで読まずに済みます。
2. 引数で、その回に使う値を渡す
function greet(name) {
console.log(`こんにちは、${name}さん`);
}
greet("Aoi");
greet("Ren");
Aoiさん、Renさんの順にあいさつします。
関数へ渡す値を引数(ひきすう)と呼びます。定義のnameは、その値を受け取るための名前です。厳密には定義側を仮引数、呼び出し側を実引数と呼び分けます。
greet()と何も渡さなければ、nameはundefinedになります。この例ではこんにちは、undefinedさんと表示されます。JavaScriptは、引数が足りないからといって必ず呼び出し時に止めてくれるわけではありません。
3. returnで結果を受け取る
function add(a, b) {
const result = a + b;
return result;
}
const answer = add(3, 5);
console.log(answer);
結果は8です。値の流れを5段階にします。
add(3, 5)を呼ぶ- aが3、bが5になる
- resultが8になる
- returnで8を返す
- 呼び出し元のanswerが8になり、表示する
返す値が戻り値です。戻り値があると、add(3, 5) * 2のように次の計算にも使えます。
このaddは数値を渡す想定です。add("3", 5)なら文字列の連結で"35"になります。関数へ入れれば型の問題がなくなるわけではありません。
4. console.logとreturnは役割が違う
function showSum(a, b) {
console.log(a + b);
}
const answer = showSum(3, 5);
console.log(answer);
8
undefined
最初の8は、関数内のconsole.logが表示したものです。でも、この関数には結果を返すreturnがないので、呼び出し結果はundefinedです。
「画面に出たのだから、answerにも入るはず」と思いやすいですが、表示と受け渡しは別です。計算した値をあとで使いたいならreturn、途中経過を確認したいならconsole.logと使い分けます。
MDNの関数ガイドでも、引数と戻り値の関係を確認できます。
returnの直後で関数は終わる
function larger(a, b) {
if (a >= b) {
return a;
}
return b;
}
最初のreturnへ来たら、下のreturnへは進みません。この早い終了を使って、条件ごとに結果を返せます。
また、returnだけの行で改行し、次の行へ返したい式を書くと、セミコロンの自動挿入でundefinedを返す場合があります。return a + b;のように、返す式を同じ行から書きます。
5. アロー関数も、段階的に読めば大丈夫
const add = (a, b) => {
return a + b;
};
console.log(add(3, 5));
これがアロー関数です。=>の左に受け取る値、右に処理を書きます。
1つの式の結果を返すだけなら、次のようにも書けます。
const add = (a, b) => a + b;
console.log(add(3, 5));
波かっこを省いたこの形では、式の結果を自動で返します。ところが、(a, b) => { a + b; }では波かっこがあり、returnもないのでundefinedになります。
省略は、元の形が分かってから使えば十分です。「短いほどよい」ではなく、自分で読み直せる形にしましょう。
functionと完全に同じではない
アロー関数は、thisの扱いなどが通常のfunctionと異なります。単純な計算では同じように使えますが、どこでも機械的に置き換えられるわけではありません。
また、const add = ...は宣言前に呼べません。今回のシリーズでは、定義してから呼ぶ順番へそろえます。
6. スコープは、変数名を使える範囲
function makeMessage(name) {
const message = `こんにちは、${name}さん`;
return message;
}
const result = makeMessage("Aoi");
console.log(result);
messageは関数内だけで使える名前です。関数の外でconsole.log(message)とすると、外側に別のmessageがない限りReferenceErrorになります。
そのため、外で必要な値を戻り値として受け取っています。スコープは名前の見える範囲で、変数の値を受け渡す方法とは別に考えます。
letとconstは、ifやforなどの波かっこの中でも範囲が区切られます。古いコードのvarはルールが違うため、まずletとconstを使います。
7. 数値を渡しても、元の変数は変わらない
function addOne(number) {
number++;
return number;
}
let count = 3;
const next = addOne(count);
console.log(count);
console.log(next);
結果は3と4です。関数内のnumberを変えても、呼び出し元の数値変数countを直接変更したことにはなりません。
countを更新したいならcount = addOne(count);と、返された値を代入します。
ただし、オブジェクトを引数にした場合は同じオブジェクトのプロパティを変更できるため、数値と同じ感覚で読まないようにします。次回で例を見ます。
関数を渡す場面もある
greetは関数そのもの、greet()は今すぐ呼び出した結果です。この違いは、クリック時の処理を登録するときに重要になります。
今回は呼び出しを練習し、イベントの回で「後で呼んでもらうために関数を渡す」使い方へ進みます。
8. 練習:大きい方を返すlargerを作ろう
2つの有限な数値を受け取り、大きい方を返してください。同じならその値を返します。
模範解答
function larger(a, b) {
if (a >= b) {
return a;
}
return b;
}
console.log(larger(12, 7));
console.log(larger(-4, -2));
console.log(larger(5, 5));
12
-2
5
解説
12と7なら最初のreturn、-4と-2なら最後のreturnへ進みます。同じ5の場合は>=がtrueなので5です。
この関数は「有限な数値を2つ受け取る」という前提です。フォーム入力の文字列やNaNを何でも受け付ける設計ではありません。外から受け取るときは、呼び出す前に型変換とNumber.isFiniteなどの確認を行います。
アロー関数の別解を省略なしで書くなら、const larger = (a, b) => { if (a >= b) { return a; } return b; };です。見通しがよい方を選んで構いません。
次の一歩
関数には、入口の引数と出口の戻り値があります。「この関数は何を受け取り、何を返すか」を1文で言えると、コードの役割がはっきりします。