2021年8月27日

関数式

JavaScriptでは、関数は “魔法の言語構造” ではなく、特別な種類の値です。

前に私たちが使っていた構文は 関数宣言 と呼ばれます:

function sayHi() {
  alert( "Hello" );
}

これとは別に、関数式 と呼ばれる、関数を作るための別の構文があります。

それはこのようになります:

let sayHi = function() {
  alert( "Hello" );
};

ここでは、関数は他の任意の値と同じように明示的に変数に代入されています。どのように関数が定義されても、それは単に変数 sayHi に格納される値です。

これらのコード例の意味は同じです: “関数を作成し、変数 sayHi にそれを格納します”

alert を使ってその値を出力することもできます:

function sayHi() {
  alert( "Hello" );
}

alert( sayHi ); // 関数のコードが表示されます

sayHi の後に括弧がないので、最後の行は関数は実行されないことに注意してください。関数名への言及がその実行となるプログラミング言語も存在しますが、JavaScriptはそうではありません。

JavaScriptでは、関数は値です。そのため、それを値として扱うことができます。上のコードはその文字列表現を表示します(それはソースコードです)。

sayHi() のように呼ぶことができる点で、もちろんそれは特別な値です。

しかし、それは値なので、他のタイプの値のように扱うことができます。

関数を別の変数にコピーすることができます:

function sayHi() {   // (1) 作成
  alert( "Hello" );
}

let func = sayHi;    // (2) コピー

func(); // Hello     // (3) コピーの実行(動きます)!
sayHi(); // Hello    //     これもまだ動きます(なぜでしょう?)

上で起こっていることの詳細は次の通りです:

  1. 関数宣言 (1) で関数を生成し、変数名 sayHi に格納します。

  2. (2) でそれを変数 func にコピーします。

    改めて注意してください:sayHi の後に括弧はありません。もし括弧があった場合、sayHi関数自身 ではなく、func = sayHi()sayHi() の呼び出し結果を func に書き込みます。

  3. これで、関数は sayHi()func() どちらでも呼ぶことができます。

また、1行目で sayHi を宣言するのに関数式を使うこともできます:

let sayHi = function() { ... };

let func = sayHi;
// ...

すべて同じように動作します。何が起こっているのかより明白ですね。

なぜ末尾にセミコロンがあるのでしょう?

疑問があるかもしれません。なぜ関数式は末尾にセミコロン ; を持つのか、そして関数宣言にはそれがないのか:

function sayHi() {
  // ...
}

let sayHi = function() {
  // ...
};

答えはシンプルです:

  • コードブロックや if { ... }, for { }, function f { } などの構文構造の末尾には ; が必要ありません。
  • 関数式は文の内側で使われます: let sayHi = ...; の値として利用します。これはコードブロックではありません。セミコロン ; はどんな値であれ文の最後に推奨されています。従って、ここのセミコロンは関数式自体と関係はなく、単に文の終わりです。

コールバック関数

値として関数を渡し、関数式を使う例をみてみましょう。

私たちは、3つのパラメータを持つ関数 ask(question, yes, no) を書きます:

question
質問内容
yes
答えが “はい” の場合に実行する関数
no
答えが “いいえ” の場合に実行する関数

関数は question を聞き、ユーザの回答に合わせて、yes() または no() を呼びます:

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

function showOk() {
  alert( "You agreed." );
}

function showCancel() {
  alert( "You canceled the execution." );
}

// 使用法: 関数 showOk, showCancel は ask の引数として渡されます
ask("Do you agree?", showOk, showCancel);

これをもっと簡単に書く方法を探る前に、ブラウザ(と場合によってはサーバ側)では、このような関数は非常に一般的であること留意しましょう。実際の実装と上の例の主な違いは、実際の関数は単純な confirm よりも、より複雑な方法でユーザとやり取りをすることです。ブラウザでは、通常このような関数は見栄えのよい質問ウィンドウを描画します。が、それはまた別の話です。

askの引数の showOkshowCancelコールバック関数 または単に コールバック と呼ばれます。

このアイデアは、渡した関数が必要に応じて後から “コールバック” されることを期待するというものです。このケースでは、showOK は “はい” のためのコールバック関数になり、showCancel は “いいえ” の回答のためのコールバック関数です。

同じ関数をより短く書くために関数式を使うことができます:

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

ask(
  "Do you agree?",
  function() { alert("You agreed."); },
  function() { alert("You canceled the execution."); }
);

ここでは、関数は ask(...) 呼び出しの中で正しく宣言されています。これらは名前を持たないので 無名関数 と呼ばれます。このような関数は、変数に割り当てられていないため ask の外側からはアクセスできませんが、ここでは私たちにとってちょうどよいものとなっています。

このようなコードはスクリプトの中で自然に現れます。それは JavaScript の精神に基づいています。

関数は “アクション” を表す値です

文字列や数値のような通常の値は データ を現します。

関数は アクション として認識されます。

変数間で渡し、必要な時に実行させることができます。

関数式 vs 関数宣言

関数宣言と関数式の違いを明確に述べてみましょう。

まず、構文です:

  • 関数宣言: メインのコードフローで別の文として宣言された関数

    // 関数宣言
    function sum(a, b) {
      return a + b;
    }
  • 関数式: 式の内部、または別の構文構造の中で作れらた関数。ここでは、関数は “代入式” = の右側で作られます:

    // 関数式
    let sum = function(a, b) {
      return a + b;
    };

よりささいな違いは、関数がJavaScriptエンジンによって 作られたとき です。

関数式は、実行がそれに到達した時に作られ、それ以降で利用可能になります。

一度実行フローが代入 let sum = function… の右辺へ渡ったら – 関数は作られ、そこから使えるようになります(代入や呼び出しなど)。

関数宣言は異なります

関数宣言はスクリプト/コードブロック全体で使用できます。

つまり、JavaScriptがスクリプトまたはコードブロックの実行の準備をする時、最初にその中の関数定義を探し、関数を生成します。それは “初期化段階” と考えることができます。

そして、すべての関数宣言が処理されたあと、実行が続けられます。

結果的に、関数宣言として宣言された関数は、関数が定義されている場所よりも前で呼ぶことができます。

例えば、これは動作します:

sayHi("John"); // Hello, John

function sayHi(name) {
  alert( `Hello, ${name}` );
}

関数宣言 sayHi は、JavaScriptがスクリプトの開始の準備をしているときに生成され、その中でどこからでも見えます。

…もしもそれが関数式だった場合、動作しないでしょう:

sayHi("John"); // エラー!

let sayHi = function(name) {  // (*) no magic any more
  alert( `Hello, ${name}` );
};

関数式は、実行がそれに到達した時に作られます。それは行 (*) で起こります。遅すぎます。

関数宣言がコードブロックの中で作られるとき、そのブロックの内側であればどこからでも見えます。しかし、その外側からは見えません。

必要とされるブロックの中だけでローカル変数を宣言することは、時には便利です。しかし、その機能も問題を引き起こす可能性があります。

例えば、ランタイムの中で得た age 変数に依存する関数 welcome() を宣言する必要があるとしましょう。そして、しばらくしてから使用する予定だとします。

下のコードはうまく動作しません:

let age = prompt("What is your age?", 18);

// 条件付きで関数を宣言する
if (age < 18) {

  function welcome() {
    alert("Hello!");
  }

} else {

  function welcome() {
    alert("Greetings!");
  }

}

// ...後で使う
welcome(); // エラー: welcome は未定義です

なぜなら、関数宣言は、それが存在するコードブロックの内側でのみ見えるからです。

別の例です:

let age = 16; // 例として16

if (age < 18) {
  welcome();               // \   (実行)
                           //  |
  function welcome() {     //  |
    alert("Hello!");       //  |  関数宣言はそれが宣言されたブロックの中であれば
  }                        //  |  どこでも利用可能です
                           //  |
  welcome();               // /   (実行)

} else {

  function welcome() {     //  age = 16 の場合, この "welcome" は決して作られません
    alert("Greetings!");
  }
}

// ここは、波括弧の外です
// なのでその中で作られた関数宣言は見ることができません

welcome(); // エラー: welcome は定義されていません

if の外側で welcome を見えるようにするためにはどうしたらよいでしょうか?

正しいアプローチは、関数式を使い、welcomeif の外で宣言し、適切なスコープをもつ変数に代入することです。

これは、意図したとおりに動作します:

let age = prompt("What is your age?", 18);

let welcome;

if (age < 18) {

  welcome = function() {
    alert("Hello!");
  };

} else {

  welcome = function() {
    alert("Greetings!");
  };

}

welcome(); // ok now

もしくは、疑問符演算子 ? を使うことでさらにシンプルにできます:

let age = prompt("What is your age?", 18);

let welcome = (age < 18) ?
  function() { alert("Hello!"); } :
  function() { alert("Greetings!"); };

welcome(); // ok now
関数宣言と関数式のどちらを選択するのか?

経験則として、関数を宣言する必要があるとき、最初に考えるのは関数宣言構文です。関数が宣言される前に呼ぶことができるため、コードを体系化する自由度が増します。

また、コードの中で、let f = function(…) {…} よりも function f(…) {…} の方が調べるのが少し簡単です。関数宣言はより “目を引きます”。

…しかし、関数宣言が幾つかの理由で適していない場合(上でみた例)、関数式を使用するべきです。

サマリ

  • 関数は値です。それらはコード上のどの場所でも、割り当て、コピー、宣言をすることができます。
  • 関数がメインのコードフローの中で別の文として宣言されていたら、それは “関数宣言” と呼ばれます。
  • 関数が式の一部として作られたら、それは “関数式” と呼ばれます。
  • 関数宣言は、コードブロックが実行される前に処理されます。ブロックの中ではどこからでも見えます。
  • 関数式は、実行フローがそれに到達した時に作られます。

たいていのケースでは、関数の宣言が必要な場合、関数宣言が望ましいです。なぜなら、それ自身の宣言の前でも利用することができるからです。これにより、コード構成の柔軟性が増し、通常は読みやすくなります。

従って、関数宣言がそのタスクに適さない場合にのみ関数式を使うべきです。この章ではそのような例をいくつか見てきましたが、今後もさらに多くの例を見ていくことになるでしょう。

チュートリアルマップ