最近はどんな小さなアイデアも JavaScript でとりあえず形にするように心がけています。
今回は JavaScript でクラス(のようなもの)を定義する方法を整理してみました。なぜクラスではなく「のようなもの」であるかも説明していきたいと思います。
オブジェクトリテラルでクラスを定義する
クラスとは、特定のメソッドとメンバ変数を持つオブジェクトのことです。
JavaScript での単純なクラスの定義は、オブジェクトリテラルを使用しオブジェクトを定義するもので、これはよく目にするパターンだと思います。
クラスの概念とオブジェクトリテラルでの定義
[code]
var sample = {
name: ‘sample_name’,
alert_hello: function() {
alert(‘Hello’);
}
};
console.log(sample.name);
sample.alert_hello();
[/code]
新しいオブジェクトを生成したい場合には、上記の sample を プロトタイプ とすることで実現が可能です。(※プロトタイプについては後述します)
[code]
if ( typeof Object.create !== ‘function’ ) {
Object.create = function(o) {
var F = function() {};
F.prototype = o;
return new F();
};
}
var s = Object.create(sample);
s.alert_hello();
[/code]
コンストラクタでクラスを定義する
オブジェクトリテラルではなく、Java や C++ 等のクラスベースでのオブジェクト指向のようにクラスを定義する場合は以下のようになります。
クラスの定義とインスタンスの生成
一見ただの関数定義ですが、クラスなので頭文字を大文字にします。インスタンスの生成には new を使用します。関数内に return を使用してはいけません。その理由は後述します。
[code]
function SampleClass(){
console.log(‘Hello!’);
}
var sample = new SampleClass();
[/code]
コンストラクタと引数の受け渡し
そもそも JavaScript にクラスの概念はありません。実は先ほどクラスとして定義したものはコンストラクタです。
コンストラクタとは、インスタンスが生成されたときに実行される関数のことで、それをここではクラスと呼んでいます。コンストラクタは関数なので引数で値を渡すことができます。
[code]
function SampleClass(str){
console.log(str);
}
var sample = new SampleClass(‘こんにちは!’);
[/code]
メンバ変数の定義とアクセス
メンバ変数は this キーワードを使って定義し、インスタンスから . (ドット)でアクセスします。少しクラス感が出てきました(笑)
[code]
function SampleClass(str){
this.myString = ‘Hello!’;
}
var sample = new SampleClass();
console.log(sample.myString);
[/code]
new を使用しインスタンスを生成したとき、JavaScript では暗黙的に関数内で以下ような2行が追加されています。this キーワードを使用しメンバ変数を定義することや、先ほどのreturn を使用してはいけない理由は、return を別のオブジェクトで定義すれば this がリターンされなくなるためです。
[code]
function SampleClass(str){
//var this = {};
this.myString = ‘Hello!’;
//return this;
}
var sample = new SampleClass();
console.log(sample.myString);
[/code]
インスタンスメソッドの定義と問題点
メンバ変数に無名関数として処理を代入すれば、インスタンスメソッドとして機能します。
ここではメンバ変数へ値を引き渡すセッターメソッドと、メンバ変数を取得するゲッターメソッドを定義します。
[code]
function SampleClass(str){
this.myString = str;
this.setString = function(str){
this.myString = str;
}
this.getString = function(){
return( this.myString );
}
}
var sample = new SampleClass(‘Hello’);
console.log(sample.myString);
sample.setString(‘こんにちは’);
alert(sample.getString());
[/code]
ただしこの場合、インスタンスが生成される度にインスタンスメソッドが保持され、メモリの領域を圧迫します。この問題は次に説明する prototype で解決されます。
prototype について
prototype とは、関数オブジェクトが定義された時点で自動的に作成されるメンバ変数です。その prototype は空オブジェクトへの参照を保持しています。インスタンス内に存在しないメンバ変数が参照された場合、prototype へ値を探しに行きます。このことから、prototype プロパティは自分の親への参照のような存在と考えることが出来ます。
次の例ではクラスの中身は空ですが、 prototype に対し show プロパティを定義することで、インスタンス sample から show を呼び出しています。
prototype は SampleClass クラスの親のような存在なので、prototype 自身にプロパティを定義すれば、SampleClass のインスタンスである sample からも show を呼び出すことが出来ます。
[code]
function SampleClass(){
}
SampleClass.prototype.show = function(){
console.log(‘Hello!’);
}
var sample = new SampleClass();
sample.show();
[/code]
クラス(コンストラクタ)とプロトタイプの使用例
先ほどのメンバ変数へ値を引き渡すセッターメソッドと、メンバ変数を取得するゲッターメソッドを連想配列にし prototype により書き換えると次のようになります。
[code]
function SampleClass(str){
this.myString = str;
}
SampleClass.prototype = {
setString: function(str){
this.myString = str;
},
getString: function(){
return(this.myString);
},
}
var sample = new SampleClass(‘Hello’);
console.log(sample.myString);
sample.setString(‘こんにちは’);
var member = sample.getString();
console.log(member);
[/code]
こうすることでインスタンスが生成される度にインスタンスメソッドが保持されることもなく、クラスのメンバ変数を利用した関数をインスタンスごとに持たせる必要がありません。
JavaScript のクラス定義のまとめ
ここで説明したパターンが全てではありませんが、prototype に汎用的な関数を定義し、関数以外の変数はメンバ変数としてクラス内に定義すればいいかと思います。
まとめ
Objective-C、C# のようなクラスベースのオブジェクト指向言語と、JavaScript のプロトタイプベースのオブジェクト指向の違いを見てみるとあらためて JavaScript の柔軟さが理解できます。
枯れた技術の水平思考とは JavaScript のことを指すのかも知れません。
この記事がみなさんのお役に立ちましたら、下記「Share it」よりブックマークやSNSで共有していただければ幸いです。