JavaScriptでインスタンス生成
var KobitoDukan = function(name) {
this.name = name;
}
var kobito = new KobitoDukan("クサマダラオオコビト");
JavaScriptでインスタンスが生成できることをご存知でしょうか。
インスタンス生成といえば、私はJavaを連想します。Javaではクラスの初期化をしますが、JavaScriptにはクラスというものがないと思っていたので、インスタンスが生成できるとは知りませんでした。
インスタンス生成について知らなくても、簡単な画面の操作や関数を使用するのに困ることはないし、大体のことはできるのですが、私の場合、jQueryプラグインを作成する際にインスタンス生成が役に立ちました。
jQueryプラグインの作成をはじめ、様々なシチュエーションで、何らかの役にたつかもしれないインスタンス生成の仕組みについて、これから詳しくみていきましょう。
JavaScriptのクラス
Javaではクラスを初期化といいましたが、JavaScriptでも同じくクラスを初期化します。しかし、JavaScriptでは明示的にClass Hige
などと定義することはありません。(でもclassは予約語)
それではJavaScriptのクラスとはどういったものなのでしょうか?
最初のサンプルでfunctionをnewしています。functionといえば関数のこと、だと思っているかもしれませんが、それは思い込みかもしれません。
この場合、newするfunctionがクラスであり、コントラクタとなります。
インスタンス生成のキーワード
- this
- プロパティ
- prototype
JavaScriptのインスタンス生成について調べてみると、プロトタイプベースのオブジェクト思考だといった概念的なものや、prototypeと__proto__
の違いなどキーワードが多数出てきます。ここでは私が思う重要だと思う3つのキーワードについて解説します。
thisとプロパティ
var KobitoDukan = function(name) {
this.name = name;
}
var kobito = new KobitoDukan("クサマダラオオコビト");
console.log(kobito.name);
this
はインスタンス生成でなくとも登場するキーワードですが、クラスの定義では、this.プロパティ名
とすることで、生成されたオブジェクトにプロパティを付与することができます。上記の場合、name
がプロパティとなります。
属性とプロパティ
プロパティも属性の一種なのでどちらも属性といえますが、簡単にいえば、HTML上の属性を一般的に属性、JavaScript上の属性をプロパティと呼びます。
HTML上の属性はあらかじめ定義されているものであり、自由に定義可能なものというわけではありません。それに対し、プロパティは自由に定義することができます。
最近ではHTML5でHTMLの要素がdata-xxx
といった自由に定義可能な値を持つことができるようになりました。属性について、詳しくはコチラをご確認ください。
jQueryではこれら属性を操作するための関数が用意されています。
- 属性(プロパティを含む)を操作する:attr
- プロパティを操作する:prop
-
data-xxx
を操作する:data
<div id="sample" width="100" height="100"></div>
$("#sample").attr("width"); // 値取得
$("#sample").attr("height", 200); // 値設定
attr = attributeのことで日本語で属性のことです。
優秀なattr
はなんでも設定できるし、設定したものはなんでも取得できます。そういう意味で、attr
はプロパティにも対応しています。
var div = function(width, height) {
this.width = width;
this.height = height;
}
var sample = new div(100, 100);
HTMLのdivと同じ構造のクラスを作成しました。HTML上の属性とプロパティが似た構造にあることが分かると思います。
少なくともjQueryのattr
からすればどちらも変わりはありません。
$(sample).attr("width");
$(sample).attr("height", 200);
HTML要素の時と同様に、値(プロパティ)の取得、変更ができます。
ただし一般的にinput要素のためにあるとかなんとか言われているprop
ですが、オブジェクトを操作する場合は、attr
ではなくprop
を使用するのが正しいかもしれません。
prop
およびdata
の詳細についてはここでは割愛します。
prototype
インスタンス化を使用しない場合、あまり目にする機会はないと思いますが、prototypeは、JavaScriptにおいて、多くの要素で使用されています。
prototypeの定義方法
KobitoDukan.prototype.name = "リトルハナガシラ";
上記のようにprototypeに定義したものが、__proto__
の中に格納されます。
そのため、インスタンスを生成した場合、生成されたオブジェクトをFirebugなどで確認すると__proto__
を確認することができます。
単一の場合は、上記でも構いません。複数ある場合は、以下のようにします。
KobitoDukan.prototype = {
"name" : "リトルハナガシラ",
isName : function() {
alert(this.name);
}
};
カンマ区切りで複数定義できます。関数も定義可能です。
jQueryのprototype
jQueryで以下のような記述を目にしたことはあるでしょうか。
$.fn.extend({
});
$.fn.name = "";
$.extend
と$.fn.extend
は違います。見ればわかりますが、$
の後になにやらfn
というものがあります。このfn
はprototypeをさしており、$.fn
を$.prototype
としても同じ結果となります。
インスタンスを生成しよう
例えば、要素A、要素B、要素Cがあるとして、それぞれ共通の処理を付加するとしましょう。
一つ一つ定義してもいいですが、同じことを何度も定義するのはダルいですね。
jQueryをみてみると、$("#hogehoge")
のようにするだけで、どんな要素でもjQueryが定義した関数を使うことができるようになります。
もしこれを一つ一つ定義しているとしたら、大変な情報量となってしまうでしょう。
それでは、インスタンスを生成した場合としない場合でどのような動きの違いがあるのか見てみましょう。
まずはインスタンスを生成しない場合から。
var dukan = {
"name" : "リトルハナガシラ",
isName : function() {
alert(this.name);
}
};
prototypeの定義と似ていますが、こちらはオブジェクト形式で定義したものです。
これをそれぞれの要素に付加した場合、一見うまくいったように見えますが、それぞれの要素は、共通の値を参照しているため、要素Aの変更が要素BにもCにも影響してしまいます。
var elementA = dukan;
var elementB = dukan;
elementA.name = "ナツノツマミ";
console.log(elementB.name):
// "ナツノツマミ"
elementAの値を変更しているようですが、この場合、dukanオブジェクトの値を変更したことになります。
そのため、同じくdukanを参照しているelementBの値も変更されてしまいました。
一方、インスタンスを生成した場合もみてみましょう。
クラスの定義は省略します。
var elementA = new KobitoDukan("クサマダラオオコビト");
var elementB = new KobitoDukan("リトルハナガシラ");
elementA.name = "ナツノツマミ";
console.log(elementB.name):
// "リトルハナガシラ"
先ほどの場合と異なる結果になりました。
newすることで、新たにオブジェクトが生成されました。そのため、elementAを変更しても、elementBに影響することはありません。
prototypeの使いどころ
オブジェクトを使った例で共通の値を参照すると説明しましたが、インスタンスを生成する際も共通の値や関数を使用したくなることはあります。
そうした場合に、prototypeを使用します。
prototypeを使用しなくてもクラスのプロパティとして定義すれば違いはないように思えますが、メモリの観点からみれば大きな違いがあります。仮にプロパティに共通的な関数を定義してしまうと、要素が増えれば増えるほど、同じものが量産されてしまうことになります。共通して使用するものはprototypeを使用しましょう。
じゃあ個別に変更したくなったらどうするんだ、と思うかもしれませんが、そうした場合、個別に同名関数をプロパティに定義することができます。
プロパティは、prototypeよりも先に参照されるため、個別に処理を変えることができます。
var Are = function() {};
Are.prototype.noone = function() {
console.log(1);
}
var you = new Are();
you.noone = function() {
console.log(2);
}
you.noone();
// => 2
var they = new Are();
they.noone();
// => 1
prototypeの値を書き換えた訳じゃないというところがポイントです。