JavaScriptでは、関数は「魔法の言語構造」ではなく、特別な種類の値です。
前に使用した構文は関数宣言と呼ばれます:
function sayHi() { alert( "Hello" );}
関数を作成する別の構文があります。これは関数式と呼ばれる構文です。
let sayHi = function() { alert( "Hello" );};
ここで、関数は他の値のように明示的に作成されて変数に代入されます。 関数をどのように定義しても、それは変数 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) copyfunc(); // 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
そのものではなく、sayHi()
の呼び出しの結果をfunc
に書き込むことになる。 - これで関数は
sayHi()
とfunc()
の両方として呼び出すことができる。
最初の行で、sayHi
を宣言するために関数式を使用することもできたことに注意してください:
let sayHi = function() { alert( "Hello" );};let func = sayHi;// ...
すべては同じように機能します。
Function Expression には最後にセミコロン ;
があるのに、なぜ Function Declaration にはないのか、と思うかもしれません。
function sayHi() { // ...}let sayHi = function() { // ...};
その答えは単純です。
if { ... }
,for { }
,function f { }
などのように;
を使用するコードブロックや構文構造の末尾にある;
は必要ありません。- ステートメント内部でFunction Expressionが使用されている。
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." );}// usage: functions showOk, showCancel are passed as arguments to askask("Do you agree?", showOk, showCancel);
実際にはこのような関数は非常に有用である。 現実のask
と上記の例との大きな違いは、現実の関数は単純なconfirm
よりも複雑な方法でユーザーと対話することである。 ブラウザでは、このような関数は通常、見栄えの良い質問ウィンドウを描きます。
ask
の引数showOk
とshowCancel
はコールバック関数または単にコールバックと呼ばれる。
アイデアは、関数を渡して、必要なら後で「コールバック」されることを期待することである。 この場合、showOk
は「はい」の答えのコールバックになり、showCancel
は「いいえ」の答えのコールバックになります。
同じ関数をもっと短く書くために関数式を使うことができます。 これらは名前を持たないので、匿名 (anonymous) と呼ばれる。 このような関数は ask
の外部からはアクセスできませんが (変数に代入されないため)、それはここで私たちが望んでいることです。
文字列や数値などの通常の値はデータを表します。
関数はアクションとして認識できます。
私たちは変数間でそれを渡すことができ、望むときに実行できます。
Function Expression vs Function Declaration
Function Declaration と Expressation の主な違いを定式化しましょう。
まず構文:コード内で両者をどのように区別するかについてです。
-
Function Declaration: 関数、メイン コード フローで、個別のステートメントとして宣言します。
// Function Declarationfunction sum(a, b) { return a + b;}
-
Function Expression: 関数、式または他の構文構成の内部に作成します。 ここでは、関数は “代入式”
=
:// Function Expressionlet sum = function(a, b) { return a + b;};
より微妙な違いは、関数が JavaScript エンジンによって作成される場合です。
Function Expression は実行がそれに達した時に作成されて、その瞬間からしか使用できなくなります。
実行フローが割り当て let sum = function…
– Here we go の右側を通過すると、関数が作成され、これから使用 (割り当て、呼び出しなど) できる。
Function Declaration は異なるものです。
A Function Declaration は定義されるよりも早く呼び出すことができます。
たとえば、グローバル Function Declaration は、それがどこにあっても、スクリプト全体で見ることができます。
これは内部アルゴリズムによるものです。 JavaScript はスクリプトを実行する準備をするとき、まずその中にあるグローバル関数宣言を探し、関数を作成します。 これは「初期化段階」と考えることができます。
そして、すべてのFunction Declarationが処理された後、コードが実行されるのです。
たとえば、次のように動作します。
sayHi("John"); // Hello, Johnfunction sayHi(name) { alert( `Hello, ${name}` );}
Function Declaration sayHi
は JavaScript がスクリプトを開始する準備中に作られ、その中のどこでも見ることができるようになります。
…If it were a Function Expression, then it wouldn’t work:
sayHi("John"); // error!let sayHi = function(name) { // (*) no magic any more alert( `Hello, ${name}` );};
Function Expressions are created when the execution reached them. それが起こるのは(*)
行だけです。
Function Declaration のもうひとつの特別な機能は、そのブロック スコープです。
Strict モードでは、Function Declaration がコード ブロック内にある場合、そのブロック内のどこでも表示されます。
例えば、実行時に取得する age
変数に依存する welcome()
関数を宣言する必要があると想像してみましょう。
Function Declaration を使用すると、意図したように動作しません。
let age = prompt("What is your age?", 18);// conditionally declare a functionif (age < 18) { function welcome() { alert("Hello!"); }} else { function welcome() { alert("Greetings!"); }}// ...use it laterwelcome(); // Error: welcome is not defined
Function Declaration はそれが存在するコードブロック内でのみ見えるからです。
ここに別の例を示します。
let age = 16; // take 16 as an exampleif (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
経験則では、関数を宣言する必要がある場合、最初に検討するのは関数宣言構文です。
また、let f = function(…) {…};
よりも function f(…) {…}
をコードで探す方が簡単なので、読みやすさの点でも優れています。 関数宣言はより「目を引く」ものです。
…しかし、何らかの理由で関数宣言が適していない場合や、条件付き宣言が必要な場合(先ほど例を見てきました)、関数式を使用すべきです。
概要
- 関数は値であります。 それらはコードのどの場所でも割り当て、コピー、または宣言することができます。
- 関数がメイン コード フローの個別のステートメントとして宣言される場合、それは “関数宣言” と呼ばれます。 それらはブロック内のどこでも見ることができます。
- Function Expressions は実行フローがそれらに到達したときに作成されます。
関数を宣言する必要がある場合、宣言自体の前に見えるので、ほとんどのケースでは Function Declaration が望ましいです。 そのため、Function Declaration がタスクに適していない場合にのみ、Function Expression を使用する必要があります。 この章ではそのような例をいくつか見てきましたが、将来的にはもっと多くの例を見ていくことになるでしょう。