JavaScriptでは、関数は「魔法の言語構造」ではなく、特別な種類の値です。
これまで使用してきた構文は、関数宣言と呼ばれています。
function sayHi() {
alert( "Hello" );
}
関数式と呼ばれる、関数を作成するための別の構文があります。
これは、任意の式の途中で新しい関数を作成することができます。
例えば
let sayHi = function() {
alert( "Hello" );
};
ここでは、変数`sayHi`が、`function() { alert("Hello"); }`として作成された新しい関数の値を取得していることがわかります。
関数の作成は代入式(`=`の右側)のコンテキストで行われるため、これは関数式です。
`function`キーワードの後に名前がありません。関数式では、名前を省略することができます。
ここではすぐに変数に代入しているので、これらのコードサンプルの意味は同じです。「関数を作成し、それを変数`sayHi`に格納する」。
後述する、より高度な状況では、関数は作成後すぐに呼び出されたり、後で実行されるようにスケジュールされたりすることがあります。どこにも格納されず、匿名のままです。
関数は値です
繰り返しますが、関数がどのように作成されても、関数は値です。上記の両方の例では、`sayHi`変数に関数を格納しています。
`alert`を使用して、その値を出力することさえできます。
function sayHi() {
alert( "Hello" );
}
alert( sayHi ); // shows the function code
`sayHi`の後に括弧がないため、最後の行は関数を*実行しません*。関数名に言及するだけで実行されるプログラミング言語もありますが、JavaScriptはそうではありません。
JavaScriptでは、関数は値なので、他の種類の値と同様に扱うことができます。上記のコードは、ソースコードである文字列表現を示しています。
確かに、関数は`sayHi()`のように呼び出すことができるという意味で、特別な値です。
しかし、それでも値です。そのため、他の種類の値と同様に扱うことができます。
関数を別の変数にコピーすることができます
function sayHi() { // (1) create
alert( "Hello" );
}
let func = sayHi; // (2) copy
func(); // Hello // (3) run the copy (it works)!
sayHi(); // Hello // this still works too (why wouldn't it)
上記で何が起こっているかを詳しく説明します。
- 関数宣言`(1)`は関数を作成し、それを`sayHi`という名前の変数に格納します。
- 行`(2)`はそれを変数`func`にコピーします。繰り返しますが、`sayHi`の後に括弧はありません。もし括弧があれば、`func = sayHi()`は`sayHi()`*呼び出しの結果*を`func`に書き込み、*関数*`sayHi`自体を書き込みません。
- これで、関数は`sayHi()`と`func()`の両方で呼び出すことができます。
最初の行で、関数式を使用して`sayHi`を宣言することもできました。
let sayHi = function() { // (1) create
alert( "Hello" );
};
let func = sayHi;
// ...
すべて同じように動作します。
関数式には最後にセミコロン`;`がありますが、関数宣言にはないのはなぜか疑問に思うかもしれません。
function sayHi() {
// ...
}
let sayHi = function() {
// ...
};
答えは簡単です。関数式は、代入文:`let sayHi = …;`の中で`function(…) {…}`として作成されます。セミコロン`;`は文の最後に推奨されますが、関数構文の一部ではありません。
セミコロンは、`let sayHi = 5;`のような単純な代入にも、関数代入にも存在します。
コールバック関数
関数を値として渡し、関数式を使用する例をもっと見てみましょう。
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." );
}
// usage: functions showOk, showCancel are passed as arguments to ask
ask("Do you agree?", showOk, showCancel);
実際には、このような関数は非常に便利です。現実の`ask`と上記の例との主な違いは、現実の関数は単純な`confirm`よりも複雑な方法でユーザーと対話することです。ブラウザでは、そのような関数は通常、見栄えの良い質問ウィンドウを描画します。しかし、それは別の話です。
`ask`の引数`showOk`と`showCancel`は、コールバック関数または単にコールバックと呼ばれます。
関数を渡し、必要に応じて後で「コールバック」されることを期待するという考え方です。この場合、`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の精神に則っています。
文字列や数値などの通常の値は、データを表します。
関数は、アクションとして認識できます。
変数間で渡したり、必要なときに実行したりできます。
関数式と関数宣言
関数宣言と関数式の主な違いをまとめてみましょう。
まず、構文:コードでそれらを区分する方法。
-
関数宣言:メインコードフローで、独立したステートメントとして宣言された関数
// Function Declaration function sum(a, b) { return a + b; }
-
関数式:式内または別の構文構造体内で作成された関数。ここでは、関数は「代入式」`=`の右辺で作成されます。
// Function Expression 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"); // error!
let sayHi = function(name) { // (*) no magic any more
alert( `Hello, ${name}` );
};
関数式は、実行がそれらに到達したときに作成されます。それは行`(*)`でのみ発生します。遅すぎます。
関数宣言のもう1つの特別な機能は、ブロックスコープです。
厳格モードでは、関数宣言がコードブロック内にある場合、そのブロック内のどこにでも表示されます。ただし、外部には表示されません。
たとえば、実行時に取得する`age`変数に応じて、関数`welcome()`を宣言する必要があるとします。そして、後でいつかそれを使用する予定です。
関数宣言を使用すると、意図したとおりに動作しません。
let age = prompt("What is your age?", 18);
// conditionally declare a function
if (age < 18) {
function welcome() {
alert("Hello!");
}
} else {
function welcome() {
alert("Greetings!");
}
}
// ...use it later
welcome(); // Error: welcome is not defined
これは、関数宣言が、それが存在するコードブロック内でのみ表示されるためです。
別の例を次に示します。
let age = 16; // take 16 as an example
if (age < 18) {
welcome(); // \ (runs)
// |
function welcome() { // |
alert("Hello!"); // | Function Declaration is available
} // | everywhere in the block where it's declared
// |
welcome(); // / (runs)
} else {
function welcome() {
alert("Greetings!");
}
}
// Here we're out of curly braces,
// so we can not see Function Declarations made inside of them.
welcome(); // Error: welcome is not defined
`welcome`を`if`の外側で表示するにはどうすればよいですか?
正しいアプローチは、関数式を使用し、`if`の外部で宣言され、適切な可視性を持つ変数に`welcome`を代入することです。
このコードは意図したとおりに動作します
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
経験則として、関数を宣言する必要がある場合は、最初に検討すべきことは関数宣言構文です。このような関数は宣言前に呼び出すことができるため、コードの構成方法に自由度が生まれます。
また、コード内で`function f(…) {…}`を探す方が`let f = function(…) {…};`を探すよりも簡単なので、可読性も向上します。関数宣言はより「目立ち」ます。
…しかし、何らかの理由で関数宣言が適さない場合、または条件付き宣言が必要な場合(例を見てきました)、関数式を使用する必要があります。
まとめ
- 関数は値です。コードの任意の場所で代入、コピー、または宣言できます。
- 関数がメインコードフローで独立したステートメントとして宣言されている場合、それは「関数宣言」と呼ばれます。
- 関数が式の一部として作成される場合、それは「関数式」と呼ばれます。
- 関数宣言は、コードブロックが実行される前に処理されます。ブロック内のどこからでも参照できます。
- 関数式は、実行フローがそれらに到達したときに作成されます。
関数を宣言する必要がある場合、ほとんどの場合、関数宣言が推奨されます。これは、宣言自体よりも前に参照できるためです。これにより、コードの構成に柔軟性が増し、通常は可読性が向上します。
そのため、関数宣言がタスクに適していない場合にのみ、関数式を使用する必要があります。この章ではその例をいくつか見てきましたが、今後さらに多くの例を見ることになります。
コメント
<code>
タグを使用し、複数行の場合は<pre>
タグで囲み、10行を超える場合はサンドボックス(plnkr、jsbin、codepenなど)を使用してください。