いや、JavaScriptにクラスなんぞ存在しないので、あくまでクラスじゃなくてクラスもどきなんですが、最近はこんな書き方で書いてます。CoffeeScriptとかTypeScriptが生成するコードを真似たつもり。
ちゃんとnew使えるし、継承もmixinもあるよ!
特に何も継承していないクラス
/**
* Klass
*
* @constructor
*/
var Klass = new function(){
var self = function Klass() {
//コンストラクタの処理をここに書く
};
//メソッド定義。
self.prototype = {
constructor: self
,method: function method() {
}
//,method2: function method2() { }
//...
};
return self;
};
//使い方
var k = new Klass();
k.method();
console.log(k instanceof Klass); //true
console.log(k.constructor.name); //"Klass"
ポイント
-
Klass
とmethod
を置き換えて使います -
self
は何度も出てきちゃうので何か他の変数名でもよい -
new function(){ ... }
は即時関数(function(){ ... })()
の別記法です。個人的趣味。 - constructorをわざわざ代入しているのは、
constructor.name
の上書きを防止するため。 - constructor.nameはデバッグ時に結構使われるので…
- prototypeをあまり何度も書きたくないのでオブジェクトの代入方式を採用
- selfを4回も書かないとだけど泣かない
- privateなんてありません。命名規則とかで頑張る
- ナビ子記法を意識して関数名は省略しない
- 前カンマ方式でつけ忘れ防止
クラスの継承
さて、継承をしたいならもう少し複雑になります。継承を補助してくれる関数を定義しておかないとやってられません。
/**
* Object.createの拡張関数
*/
function extend(o) {
var f = extend.f, i, len, n, prop;
f.prototype = o;
n = new f;
for (i=1, len=arguments.length; i<len; ++i) {
for (prop in arguments[i]) {
n[prop] = arguments[i][prop];
}
}
return n;
}
extend.f = function(){};
これがあれば、継承はこんな感じ。KlassクラスがParentクラスを継承しているイメージです。
var Klass = new function(){
var self = function Klass() {
//親クラスのコンストラクタを呼ぶ
Parent.apply(this, arguments);
//コンストラクタをここに書く
};
var uber = Parent.prototype;
self.prototype = extend(uber, {
constructor: self
,method: function method() {
//親クラスのmethodを呼びたい場合
uber.method.apply(this, arguments);
}
//,method2: function method2() { }
//...
});
return self;
};
- 親クラスのコンストラクタは明示的に呼ぶ必要あり
- Parent.apply(...)ではなくuber.constructor.apply(...)と書いてもOK。しかし長い…
- Parent.prototypeとか何度も打ちたくないのでuberというエイリアスを作る
- 本当は
super
にしたいところだけど予約語なのでuberで妥協
ミックスイン
JavaScriptに多重継承はないのだけれど、これはメソッド群をコピーする形でナンチャッテ多重継承を実現します。
関数をまとめただけのオブジェクトを作れば、ミックスインできます。
/**
* @mixin
*/
var SomeMixin = {
hoge: function(){
}
,fuga: function(){
}
};
/**
* Klass
* Parentを継承して、SomeMixinをミックスインする形式
*
*/
var Klass = new function() {
var self = function Klass() {
//親クラスのコンストラクタを呼ぶ
Parent.apply(this,arguments);
//コンストラクタの処理を書く
};
var uber = Parent.prototype;
//親クラス, mixin1, mixin2, ... 自前のメソッド定義の順
self.prototype = extend(uber, SomeMixin, {
constructor: self
,method: function method() {
}
//,method2: function method2() { }
//...
});
return self;
};
- ミックスインは何個でもOK
- 後に書いた方が勝つので、クラス独自定義を最後に書く
例
適当な例
/**
* Animal
*
* @constructor
*/
var Animal = new function() {
var self = function Animal(name) {
this.name = name;
};
self.prototype = {
constructor: self
,breathe: function breathe() {
console.log(this.name + "「すーはー」");
}
,eat: function eat() {
console.log(this.name + "「もぐもぐ」");
}
};
return self;
};
/**
* Humanクラス
*
* @extends Animal
*/
var Human = new function() {
var self = function Human(name) {
Animal.call(this, name);
};
var uber = Animal.prototype;
self.prototype = extend(uber, {
constructor: self
/**
* Humanクラスならごはん食べる前後に挨拶する
*/
,eat: function eat() {
console.log(this.name + "「いただきます」");
uber.eat.call();
console.log(this.name + "「ごちそうさまでした」");
}
/**
* ヒトは考える
*/
,think: function think() {
console.log(this.name + "「うーん」");
}
});
return self;
};
/**
* WingMixin
*
* @mixin
*/
var WingMixin = {
fly: function fly() {
console.log(this.name + "「ぱたぱた」");
}
};
/**
* Animalを継承してWingをミックスインしてこうもりクラス
*
* @constructor
* @extends Animal
*/
var Bat = new function() {
var self = function Bat(name) {
Animal.call(this, name);
};
var uber = Animal.prototype;
self.prototype = extend(uber, WingMixin);
return self;
};