はじめに
JavaScriptはオブジェクト指向言語です。しかし利便性のために、C言語などと同じようにグローバルな関数を定義し、構造化的な手法でプログラミングすることも可能です。
この記事では、JavaScriptにおける関数の基本的な性質を見ていくと共に、関数の正体、および関数の使用方法について解説していきたいと思います。
なお、この記事はJavaScriptの解説ですが、その内容は、標準仕様のECMAScriptで扱われる範囲に基づいています。従って、同じくECMAScriptを元にしている言語(JScript、ActionScript)でも通じる内容になっています。
対象読者
プログラミングの基本的な知識、ならびにオブジェクトやメソッドと言った基礎的な概念については、ここでは解説しません。最低限、オブジェクト指向プログラミングについて理解をしている人を対象としています。
この記事は、主に以下のような人を対象としています。
- サーバサイドJavaを行っていて、JSPなどでJavaScriptの知識が必要になった人
- JavaScriptをより深く使いこなしたい人
- オブジェクト指向プログラミングを別のアプローチから捉えなおしてみたい人
- JavaScriptでクラスを作る方法は知っているが、何故そのように書くとクラスを作れるのかわからない人
必要な環境
ECMAScript 3rd editionの実装系。例えば、下記のような環境。
- Microsoft Internet Explorer 5.5以上
- Firefox 1.0以上
目次
関数もオブジェクトである
JavaScriptの関数はオブジェクトの一種です。つまり、定義されたプログラムを実行するだけでなく、メソッドやプロパティを持ち、またそれらを操作する事ができます。
とりあえず、JavaScript関数の定義法を見てみましょう(List2.1)。
function myFunc(arg) { alert(arg); } myFunc("hoge"); // hoge と表示される。
引数をダイアログで表示する簡単な関数です。
関数がオブジェクトである、というなら、コンストラクタを用いて関数を定義する事ができるのでしょうか? できます。コンストラクタとnew
式を用いて、List2.1と同じ関数を定義したのが以下のコードです(List2.2)。
var myFunc = new Function("arg", "alert(arg);"); myFunc("hoge"); // hoge と表示される。
List2.1とList2.2は全く同じことを表しています(*1)。
ここで注目して欲しいのは、List2.2において、myFunc
はvar
で宣言されている、つまりmyFunc
は変数である、という事です。これが何を表すのかと言うと、関数は、一つ一つに名前が付けられるのではなく、ただ変数に関数オブジェクトの参照を代入するだけである、という事です。
したがって、関数を他の変数に代入したりすることができるのです(List2.3)。
function funcA() { alert("関数A"); } var funcB = funcA; // 関数の代入 funcB(); // 関数A と表示される。
関数を実行するためには、その関数オブジェクトを参照している変数に括弧をつけることで、実行する事ができます(*2)。
関数オブジェクトの参照を上手く使うことで、C言語で関数ポインタを利用するようなコードも簡単に書くことができます。
メソッドもプロパティである
さて前節で、関数はオブジェクトであり、関数名はただの変数名だということを解説しました。
それでは、オブジェクトのメソッドはどうなのでしょうか。
実はこれも関数オブジェクトです。つまり、オブジェクトのメソッドというのは、単に関数オブジェクトの参照が代入されているプロパティだった、という訳です。
もっと言ってしまえば、JavaScriptのオブジェクトメンバはプロパティしか存在しない、という事です。プロパティの中で関数オブジェクトの参照を持ったものをメソッドと呼ぶ、という訳です。
従って、オブジェクトに新たなメソッドを定義したい場合、プロパティを定義するのと同じように、代入式を用いることでメソッドを定義する事ができます(List2.4)。
function func() { alert("関数です。"); } var obj = new Object(); obj.method = func; // 関数の代入 obj.method();
やろうと思えば、プログラムのある時点ではメソッドで、別のある時点では、ただのプロパティである、というようなメンバを作る事もできます。コード的に読み難くなるので止めた方がいいとは思いますが。
無名関数の定義
さて、オブジェクトのメソッドも代入式によって定義できる事を解説しました。しかし、毎回List2.4のように、関数の定義と代入を別々に行うのでは手間が掛かります。また、メソッドとしてしか使用するつもりの無い関数を、グローバルな関数で定義したままにしておくというのも、なにやら無駄な気がします。
そこで、この節では関数を定義するもう1つの方法を解説したいと思います(*3)。無名関数の定義と呼ばれるこの方法では、関数の定義と代入を同時に行うことができ、またグローバルな関数が別途に定義される事もありません。
具体的にコードを見てみましょう(List2.5)。
var obj = new Object(); obj.method = function(arg) { alert(arg); } obj.method("テスト"); // テスト と表示される
何のことは無く、function文に関数名を書かないだけですね。この様に記述する事によって、グローバルな関数の定義を避け、直接プロパティに参照を代入する事ができるのです。
オブジェクトのメソッドを定義する場合、大概はこの方法で記述する事になります。
ところで、こんな表記を使わなくてもFunction
コンストラクタを使えばいいじゃないか、と思われる方がいらっしゃるかもしれません。正直なところ、List2.5のような場合では全くその通りです。
ただ、厳密な話をするとfunction
文を用いる場合とFunction
コンストラクタを用いる場合では、違いが出てくる時があります。この違いについては後の回にて詳細に解説したいと思いますので、ここでは、基本的にメソッドを定義する時は無名関数の定義を使う、という事を覚えておいて下さい。
高階関数
前節にて関数はオブジェクトであり、変数やプロパティに代入する事ができるという事を解説しました。では、関数の引数として渡すことはどうでしょうか? もちろんこれも可能です。この様な引数に関数をとる事ができる関数の事を、高階関数といいます。関数型言語のLispやSchemeといった言語ではお馴染みの機能なのですが、これもJavaScriptでは実現することができます。
早速、具体例を見てみましょう(List2.6)。
// 配列の全要素に指定した関数を実行する function eachArray(arr, func) { for (var i=0; i<arr.length; i++) { func(arr[i]); } } var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; eachArray(array, alert);
List2.6を実行すると、配列の要素ごとにalert
が実行され、計10回ダイアログが表示されます。この様な関数を引数に取る関数を使用することで、より簡潔なコードを記述することができる様になりますので、ぜひ覚えておきましょう。
引数の秘密
前節までは、関数そのものについて見てきました。今度は、関数の使い方、引数に話を絞って見ていきたいと思います。
function
文で関数に引数を設定する事ができるのはご承知の事だと思います。それでは関数の実行時に、この宣言と一致しない引数で実行した場合、どのようになるのでしょうか?
JavaやCといった言語では、関数(メソッド)のシグネチャ通りに引数を渡さなければ、コンパイルエラーになってしまいます(*4)(List2.7)。
class MyClass { public void method(Object arg1, Object arg2) { System.out.println(arg1.toString()); System.out.println(arg2.toString()); } public static void main(String[] args) { MyClass obj = new MyClass(); obj.method(); // コンパイルエラー } }
ところが、JavaScriptではSyntaxErrorになるどころか普通に実行されてしまいます(List2.8)。
function myFunc(arg1, arg2) { alert(arg1 + ":" + arg2); } myFunc("arg1", "arg2"); // 通常の呼び出し myFunc(); // 引数の数が足りない myFunc("arg1", "arg2", "arg3"); // 引数の数が多い
JavaScriptでは、関数を実行する時の引数の数は厳密に扱われません。引数の数が足りなければ、足りない引数はundefined
として扱われます。
では、引数の数が多かった場合、渡された値はどこへ行ってしまうのでしょうか? 無視され消えてしまうのでしょうか?
実は、シグネチャよりも多くの引数を渡された場合でも、それらの値を扱う事ができます。それを可能にするのがarguments
オブジェクトです。
関数の中では暗黙にarguments
というオブジェクトが生成されます。このオブジェクトが引数で渡された値を保持しているのです。
arguments
オブジェクトはArray
インスタンス と同じように添え字で配列としてアクセスする事ができ、arguments[n-1]
が第n引数を表します(List2.9)。
function myFunc(arg1, arg2) { // arguments[0] は第一引数を指すので true alert(arg1 == arguments[0]); // シグネチャに無い 第四引数にアクセスする事ができる alert(arguments[3]); } // 二回目のアラートは arg4 と表示される。 myFunc("arg1", "arg2", "arg3", "arg4");
まとめ
今回は、以下のような関数の基本的性質とその使用方法を学びました。
- 関数はオブジェクトである。
- メソッドというのは、関数オブジェクトの参照を保持している、ただのプロパティである。
- 無名関数という関数の定義方法がある。
- 関数を引数に渡すことができる。
arguments
オブジェクトという引数を表すオブジェクトが存在する。
次回は、JavaScriptにおけるプロトタイプについて解説します。
参考資料
本稿は、Starry Night 『JavaScript講座』にて公開したものです。連載を再開するにあたり、再編集を行いCodezineに寄稿いたしました。
- Ecma International 『Standard ECMA-262』
- 『Under Translation of ECMA-262 3rd Edition』