関数の定義方法
JavaScriptで関数を定義する方法は、関数宣言と関数式の2つ。
本当はもう一つあるが、それは非推奨のものなので、ここでは省略する。
// 関数宣言 function myFunc1(){}; // 関数式 var myFunc2 = function(){};
どちらでも同じように関数が作成される。
違いとしては、関数宣言では関数の巻き上げが行われるが、関数式では行われない。
関数の巻き上げ
関数の巻き上げとは、コードの実行前に関数を読み込むこと。
この動作により、定義前の関数を呼び出すことが出来る。
function showResult(f){ console.log(f); }; showResult(myFunc1(1,2)); // 3 showResult(myFunc2(1,2)); // エラー // 関数宣言 function myFunc1(a, b){ return a+b; }; // 関数式 var myFunc2 = function(a, b){ return a+b; };
myFunc1
とmyFunc2
は同じ内容の関数だが、定義方法が違う。
関数宣言で定義したmyFunc1
は、定義されるよりも前に呼び出しているが、問題なく実行できる。
しかし、関数式で定義したmyFunc2
は、定義される前に呼び出すと、エラーになる。
Function.prototype
全ての関数は、Function
のインスタンスである。
そのため、Function.prototype
のメソッドやプロパティにアクセスできる。
主なメソッドとしては、call
やapply
がある。
プロパティとしては、length
やarguments
がある。
function myFunc1(){}; var myFunc2 = function(){}; console.log(myFunc1 instanceof Function); // true console.log(myFunc2 instanceof Function); // true console.log(Function.prototype.isPrototypeOf(myFunc1)); // true // プロトタイプとして例えば、call()やlengthがある console.log(Function.prototype.hasOwnProperty('call')); // true console.log(Function.prototype.hasOwnProperty('length')); // true
call
やapply
については下記を参照。
this, call(), apply(), bind()
lengthやarguments
どちらも、Function.prototype
が持つプロパティ。
length
length
プロパティは、その関数に定義されているパラメータの数を返す。
実際に引数がいくつ与えられるかは、無関係。
function showLength(a, b, c){ console.log(showLength.length); }; // 全て3を返す showLength(); showLength(1); showLength(1, 2); showLength(1, 2, 3, 4);
arguments
arguments
はオブジェクト。配列ではないが、配列のような使い方が出来る。
関数に渡された引数はarguments
に格納されていく。
function myFunc(){ console.log(arguments[0], arguments[1]); console.log(arguments.length); // これは、渡された引数の数を返す }; myFunc(7); // 7 undefined // 1 myFunc('a', 'b'); // a b // 2
Strictモードにおけるargumentsの挙動
Strictモードと標準モードでは、arguments
の挙動が異なる。
標準モードでは、パラメータとarguments
は、名前が異なるだけで、同一のものとして扱われる。
// 標準モード function myFunc(val){ val = 5; console.log(val, arguments[0]); arguments[0] = 9; console.log(val, arguments[0]); }; myFunc(1); // 5 5 // 9 9
だがStrictモードでは、両者は別のものとして扱われる。
'use strict' function myFunc(val){ val = 5; console.log(val, arguments[0]); arguments[0] = 9; console.log(val, arguments[0]); }; myFunc(1); // 5 1 // 5 9
但し、引数に参照型(オブジェクト)が与えられた場合は、パラメータもarguments
も同じオブジェクトを参照するため、実質的に同じものにアクセスすることになる。
'use strict' function myFunc(obj){ obj.prop = 1 console.log(obj.prop, arguments[0].prop); arguments[0].prop = 9; console.log(obj.prop, arguments[0].prop); }; myFunc({}); // 1 1 // 9 9
Strcitモードでは、arguments
に値を代入し直すことは、出来なくなる。
代入しようとすると、エラーになる。
// 標準モード function normalFunc(val){ console.log(arguments[0]); arguments = ['new arguments']; console.log(arguments[0]); }; normalFunc(1); // 1 // new arguments
'use strict' // SyntaxError: Unexpected eval or arguments in strict mode function strictFunc(val){ console.log(arguments[0]); arguments = ['new arguments']; console.log(arguments[0]); };
但し、要素の追加や変更は、Strictモードでも問題なく行える。
'use strict' function myFunc(val){ console.log(arguments[0], arguments[1]); arguments[1] = arguments[0] * 2; arguments[0] = 99; console.log(arguments[0], arguments[1]); }; myFunc(5); // 5 undefined // 99 10
オブジェクトとしての関数
JavaScriptでは、プリミティブ型以外の全てのデータ型は、オブジェクトである。
関数も当然、オブジェクトである。
そのため、プロパティを与えることが出来るし、変数に代入した際は参照渡しとなる。
function myFunc(){}; console.log(myFunc instanceof Function); // true console.log(myFunc instanceof Object); // true console.log(Function.prototype.isPrototypeOf(myFunc)); // true console.log(Object.prototype.isPrototypeOf(Function.prototype)); // true // プロパティを持たせることが出来る myFunc.prop = 1; var variable1 = myFunc; var variable2 = myFunc; console.log(variable1.prop); // 1 // variable1とvariable2は同じものを参照しているため、 // 変更は両方に反映される。 variable2.prop = 99; console.log(variable1.prop); // 99 console.log(variable2.prop); // 99
再帰
再帰とは、関数がその処理のなかで自分自身を呼び出すこと。
function myFunc(num){ console.log(num); num--; if(!(num < 0)){ myFunc(num) }; }; myFunc(3); // 3 // 2 // 1 // 0