公開日:2007.12.24
関数オブジェクト - 応用
前回で関数は文字列や他の変数に代入できる値と 同じように扱う事もできるオブジェクトの一つだという事がわかりました。 では今回は関数オブジェクトに対する理解をもう少し深めましょう。
まず関数を同じように扱えるという事は、 関数を別の関数を呼び出す引数に渡す事もできるという事です。
1: function add1(num, disp_func) {
2: disp_func(num + 1);
3: }
4: var disp = function(arg){ alert(arg) };
5: add1(5, disp); //⇒アラートで6と表示
前回、関数を変数(オブジェクトのプロパティ)にセットする と言いましたが、普通にローカル変数にもセットできます。
上記の例は4行目でdispというローカル変数に関数をセットします。 そして5行目で宣言済みのadd1の第2引数にdispを渡して実行しています。 するとadd1内、2行目で受け取った第2引数の関数オブジェクトを呼び出しています。
関数を引数に渡せるのと同様に関数の戻り値として 関数オブジェクトを返す事ができます。
1: function make_func(){
2: return function(a, b){
3: return a - b;
4: };
5: }
6: var sub = make_func();
7: alert( sub(10, 5) ); //⇒アラートで5と表示
6行目でsubという変数にmake_func()の結果を代入しています。 make_funcの結果は2~4行目の関数リテラルになります。 この関数は引数を二つ取り、それを引いた値を返します。 7行目でその関数を10と5を引数に呼び出し結果を表示しています。
関数を関数の引数や戻り値に指定できるプログラミング言語は いろいろありますがこのような関数を一般的に高階関数と呼びます。
でそれが何の役に立つの?と思われたかもしれません。 このサンプルは単純な処理なので意味はありませんが、 役に立つサンプルは追い追い紹介していこうと思います。
またJavaScriptの関数はレキシカルスコープといって、 内部で参照する変数を静的にアクセスします。 このあたりはスコープチェインなどとも関連しますので、 別の機会に説明します。今は名前だけ知っておいてください。
では実際に高階関数の使い方のサンプルをいくつか 見ていきましょう。
①配列のイテレータ関数として渡す
1: var ary = ["aaa", "bbb", "ccc"];
2: ary.each = function(iter) {
3: for (var i = 0; i < this.length; i++) {
4: iter(this[i]);
5: }
6: };
7: ary.each( function(obj){ alert(obj) } );
これが最も一般的ではないでしょうか。 1行目から5行目までで配列を作成し、 その配列にeachというメソッドを追加しています。 このメソッドの引数は関数オブジェクトをとります。
このeach関数の処理概要ですが この配列オブジェクトの長さ分ループし、引数の関数を呼び出します。 その時、その関数の引数にその時のインデックスで 指定された値を渡します。
また8行目で実際にeachメソッドを呼び出しています。 その時引数で指定されている値がクロージャです。
上記のスクリプトを実行してみましょう。 結果はアラートが3回が呼び出され、 それぞれ "aaa"、"bbb"、"ccc"と表示されたはずです。 呼び出し側では非常にシンプルな記述で、 要素毎の処理のみに注力できますね。 (ループカウンタの制御等が不要⇒不要にバグを作らない)
このような配列等、列挙タイプのオブジェクトに対して 順番に特定処理を行わせたい場合に指定する関数の事を 一般的にイテレータ関数と呼びます。
②比較関数として渡す
1: var value1 = "555";
2: var value2 = "0011";
3: function compare(comparator) {
4: if (comparator(value1, value2)) {
5: alert("value1が大きいです。");
6: } else {
7: alert("value2が大きいです。");
8: }
9: }
10: compare( function(val1, val2){ return parseInt(val1) > parseInt(val2) });
// ⇒ "value1が大きいです。"と表示
11: compare( function(val1, val2){ return val1.length > val2.length });
// ⇒ "value2が大きいです。"と表示
これも王道パターンですね。 上記の関数の実行結果はアラートで 1回目は『value1か大きいです。』 2回目は『value2が大きいです。』 と表示されるはずです。
解説すると 1, 2行目 で数値を文字列として変数に格納しています。 3行目の関数の引数がクロージャを受け取るのですが、 実際の呼び出し箇所は 10行目 となります。
10行目で引き渡しているクロージャは1, 2行目 で宣言された変数を parseInt関数によって数値型に変換した上で大小比較を行いその結果を 返します。数値型に変換されると左側にある 0 は削除されるので value1 => 555、value2 => 11 となります。 比較結果は true が返ります。
11行目の場合はそれぞれの変数の値を文字列の長さで比較しています。 それぞれ value1 => 長さ3文字、value2 => 長さ4文字 となります。 比較結果は false が返ります。
同じような処理でArrayオブジェクトにはsortという関数があります。 このsort関数の引数に比較結果を正の数、負の数で返す関数を渡してやれば 配列の中身を並び替えてくれます。
まだまだクロージャの活用方法というのはあるのですが、 長くなったので今回はこの辺までにしておきます。 今後、各サンプルの中で出てくることと思います。
まとめ
- 関数は他の関数を呼び出す引数に指定できる。
- 関数の戻り値として関数を返す事もできる。
- 配列のループや比較等、一部分だけ振る舞いを変えたい時にすっきりとした記述ができる。
- (関数のスコープはレキシカルスコープ)※別途解説します。
