関数オブジェクト - JavaScriptの個人的なメモ
関数はオブジェクト
JavaScriptを学ぶときに、まず、覚えておかなければならないのが、「関数はオブジェクト」ということです。これを覚えておくと、プログラミングがかなり楽になってきます。
今回は、JavaScript以外のプログラミング言語を学んだ方のために、この、関数オブジェクトについて、解説していきます。個人的なメモですので、誤りがあれば、ご指摘ください。
オブジェクトは連想配列
JavaScriptのオブジェクトはハッシュ(以後、連想配列)として扱うことができます。
たとえば、プロパティの書き換えを見てみましょう。
var obj = {}; obj.a = "n"; obj.a; // 文字列の"n" obj.a = 12; obj.a // 数値の12
プロパティの名前をキーとして、値を呼び出したり、上書きすることができるのです。
さて、関数もオブジェクトなのですから、以下のように、連想配列として扱うことができます。
var obj = function() {}; obj.a = "n"; obj.a; // 文字列の"n" obj.a = 12; obj.a // 数値の12
なお、function() {}という書き方は、関数オブジェクトをつくりだすための、「関数リテラル」とよばれるものですので、必ず、覚えておいてください。今後は、この書き方を用いていきます。
メソッド
プロパティの値に関数オブジェクトを入れたら、それを「メソッド」と呼びます。
var obj = { a: function(){} }; obj.a = function() {}; obj.a(); //メソッドの呼び出し
もちろん、関数はオブジェクトですので、メソッドを付け加えることができます。
var obj = function(){}; obj.a = function(){};
prototypeプロパティ
二つの機能
関数オブジェクトのプロパティとして、あらかじめ用意されているものが、prototypeプロパティです。
このプロパティには、二つの機能が用意されています。
- プロトタイプ
- 型の実装
前者はクラスに存在しない機能ですが、、後者はクラスの一機能として、他の言語でサポートされています。それぞれの機能を見ていくことにしましょう。
プロトタイプ
prototypeプロパティを使うと、プロトタイプによる、オブジェクトの継承ができるようになります。たとえば、試しに配列オブジェクトを継承してみましょう。
var F = function() {}, obj; F.prototype = [ 0, 1 ]; obj = new F(); obj[0]; // 0 obj[1]; // 1 obj.push(12); obj[3]; // 12
プロトタイプは、クラスをサポートした、ほかの言語では再現できない機能です。続けて、クラスでおなじみの型の実装の説明をします。
型の実装
この変数objの型は、F型です。それと同時に、配列オブジェクトがArray型なので、objもArray型となります。instanceofを使って、次のように型を調べてみましょう。型が合っていれば、trueを返し、そうでなければfalseを返すはずです。
obj instanceof Array; // true obj instanceof F; // true F.prototype instanceof Array // 配列オブジェクトなので、true
instanceof演算子の技術的な説明は他の記事に譲ります(注1)。
続けて、prototypeプロパティを詳しく説明して、注意点を挙げていきます。
プロパティについて詳しく
このprototypeプロパティは、関数オブジェクトの生成時に、自動で作られます。
var func = function() {}; func.prototype; // { constructor: func }というオブジェクトが入っている
prototypeプロパティの値には、オブジェクトが入っています。どのようなオブジェクトかというと、constructorプロパティに自分自身の関数オブジェクトが入っているようなオブジェクトです。すこし、わかりにくいので、次のコードを見ると、わかりやすいかと思います。
( func.prototype.constructor === func ); // true
このオブジェクトは特別なオブジェクトではないので、注意してください。普通のオブジェクトです。
では、これを何に使うのかというと、プロトタイプに使います。プロトタイプには、new演算子で関数を実行する必要があります。
var F = function() {}; obj = new F(); // new演算子を使って、関数Fを呼び出し実行 (obj.constructor === F.prototype.constructor); // プロトタイプチェインにより、true
prototypeプロパティの値は、new演算子で関数が実行されたときに、プロトタイプとしてキャッシュされます。キャッシュされたオブジェクトは、常にプロトタイプチェイン委譲の対象となります。オブジェクトのプロパティがundefinedの場合は、キャッシュのプロパティを探していき、委譲します。この動作は、(Object型ではない) Object.prototypeにたどり着くまで、繰り返すので、「プロトタイプチェイン」と呼ばれています。
また、prototypeプロパティは、自由に、値を書き換えることが可能です。
F.prototype = { a: "hoge"}; obj = new F(); obj.a; // "hoge" F.prototype = Math; obj = new F(); obj.PI; // 3.14.... (円周率)を返す
ただし、関数オブジェクトはprototypeプロパティの値に入れても、プロトタイプチェインの委譲も継承もできません。また、文字列や数値でも委譲に問題が起きます。
F.prototype = function() {}; var obj = new F(); obj(); /* 以降、すべて例外が投げられErrorとなる */ new obj(); F.call(); F.apply();
callメソッド
callメソッドは、関数内のthisの値を任意に書き換えて、その関数を実行することができます。this = 第一引数となります。
var func = function() { return this.a; }; func.call( { a: 12 } ); // 12を返す
ここで注意しなければならないのは、thisが自分自身のオブジェクトではないことです。この点が他の言語と違うところです。
applyメソッド
applyメソッドもcallメソッドと同様に、thisの値を書き換えて、その関数を実行します。ただし、第二引数には、リストを指定します。
var func = function(a, b) { return (this.a + a + b); }; func.apply( { a: 12 }, [20, 30] ); // 62を返す
また、次のようなコードがあったとします。
var func = function(a, b) { return ( a + b ); }, func2 = function(a, b) { return func.call(this, a, b); }; func2.call( 20, 30 ); // 50を返す
関数の引数入力が面倒な人は、applyメソッドとargumentsを使って次のように書き換えることができます。
var func = function(a, b) { return (a + b); }, func2 = function(a, b) { /*callメソッドをapplyメソッドに書き換えている*/ return func.apply(this, arguments); }; func2.call( 20, 30 ); // 50を返す
結論
- 関数はオブジェクトだけど、そんなに複雑ではない
- prototypeプロパティは、二つの機能が絡み合っているので、クラスとの単純な比較は危険
- prototypeプロパティと、callメソッドとapplyメソッドは、オブジェクト指向プログラミングで、使うようになるので、覚えておくと楽
参考資料
注1 Core JavaScript 1.5 Reference - 演算子 - instanceof
https://developer.mozilla.org/ja/docs/JavaScript/Reference/Operators/instanceof