タイトルの通りの内容で、ネタとしてはすでに枯れきった感のあるテーマですが、ここでは「シンプル」ということを第一義として作ってみました。
iname という名前でgithubに公開しています。
[javascript] 名前空間とクラス継承 - iname
ここではコードの解説ではなく使い方についてまとめていきます。
また、同じことはいろんな方がやっておられます。
[JavaScript] getter/setterも使えるエコ楽なクラス定義 - もちろん継承も - private変数も
#機能概要
iname、extend、append、exdef、apdef、という5つの関数で以下の機能が構成されています。
- グローバル(windowオブジェクト直下)に公開されるオブジェクトはひとつだけとし、この公開オブジェクトを名前空間とする
名前空間の大前提ですね。
この名前空間オブジェクトの型はfunctionで、inameを使って作ります。以降は単に名前空間と記述します。
- 名前空間はクラス実装のための機能を継承している
extend、append、exdef、apdef、という4つの関数が使えるようになります。これらを使って名前空間とそのprototypeにプロパティを追加します。
- 名前空間のインスタンスは上位階層とプロトタイプチェーンがリンクしている
サンプルで詳しく説明していきます。
主な機能の概要としては以上の3点です。
それ以外には _super_
というプロパティが名前空間に追加されて、上位階層を参照します。
ではサンプルです。
#使いかた
まず最初に iname で名前空間を作ります。
iname("Root");
inameはこのスクリプトがglobal公開するfunctionです。全ての機能はinameから提供されます。
実行の結果、window下に Root という名前空間が追加されます。
iname("Root.Node"); //iname("Root")を実行していなくても実行可能
console.log( typeof window.Root ); //function
console.log( typeof window.Root.Node ); //function
名前空間の区切り文字はピリオドです。階層をひとつづつ順番に作る必要はありません。
次に、作られた名前空間をクラスと見立ててプロパティを追加していきます。
Root.extend({
Hoge: "hoge"
});
Root.Node.extend({
Fuga: function(){ return "fuga"; }
});
ふたつ目の機能 extend です。
Root および Root.Node の型はfunctionなので、prototypeを持ちます。
extendは引数に指定されたオブジェクトのメンバーをprototypeに追加します。
inameで作られた名前空間は(途中経路も含めて)全てextendが使えます。
var objR = new Root();
console.log( objR.Hoge ); //hoge
var objN = new Root.Node();
console.log( objN.Fuga() ); //fuga
console.log( objN.Hoge ); //hoge
Root と Node は継承関係にあります。
Nodeのインスタンスを作るとRootのprototypeとプロトタイプチェーンでリンクしますので、extendでRootに追加されたHogeメンバーにもアクセスすることができます。
ここまでに使用した iname と extend で重要な機能はほぼ全てです。
これで十分という場合はこれ以降は読み進めなくても差し支えありません。お疲れ様でした。
#名前空間にメンバーを追加する
inameで作られた名前空間はextendが使えるようになっていました。同様に、append、exdef、apdef、も使えるようになっています。
それぞれの機能をまとめて列挙します。
- iname …… 名前空間の作成
- extend …… prototypeにプロパティを追加
- append …… functionメンバーにプロパティを追加
- exdef …… prototypeにdefinePropertyを使用してプロパティを追加
- apdef …… functionメンバーにdefinePropertyを使用してプロパティを追加
以上です。わかる人はこれだけで何するかなんとなくわかるのではないでしょうか。
では、サンプル。
iname("Root.Node").append({
Piyo: "piyo"
});
console.log( Root.Node.Piyo ); //piyo
appendはprototypeではなく名前空間そのもののメンバーにプロパティを追加します。そのまんまですね……。
副作用は、後述するバージョン情報を使用する場合以外には特に無いので、わざわざappendを使わず直接メンバーに代入しても結果は同じです。
このサンプルでは iname の戻り値から直接 append を使っています。
戻り値は作成された名前空間ですので、メソッドチェーン的な書き方ができるというわけです。inameだけでなく、extend、append、exdef、apdef、すべて名前空間を返します。
#definePropertyを使ってメンバーを追加する
exdef と apdef はそれぞれ extend と append の亜種です。
iname("Root.Node")
.exdef({
Fuga: {
value: "fuga",
enumerable: true,
writable: true,
configurable: false
}
})
.apdef({
Piyo: {
get: function(){ return "piyo" },
configurable: false
}
});
console.log( (new Root.Node).Fuga ); //fuga
console.log( Root.Node.Piyo ); //piyo
Root.Node.Piyo = "piyopiyopiyopiyo!";
console.log( Root.Node.Piyo ); //piyo
引数のオブジェクトをそれぞれのメンバー設定する際にdefinePropertyを使用します。definePropertyについてはここでは詳しく説明しません。
Object.defineProperty 関数 (JavaScript) - MSDN
#名前空間のコンストラクタ
(2016/12/6 編集)
inameの第二引数にfunctionを渡すことで、その名前空間のコンストラクタにすることができます。
iname("Root.Node", function(){
console.log( "Root.Nodeのインスタンスです" );
});
new Root.Node(); //Root.Nodeのインスタンスです
名前空間のインスタンスはスーパークラスを継承しますので instanceof でtrueがとれます。
console.log( (new Root.Node()) instanceof Root.Node ); //true
console.log( (new Root.Node()) instanceof Root ); //true
#メンバー定義のバージョン管理
(2016/12/6 編集)
メンバーの定義名が衝突した際の制御としてバージョン名を個別に設定することができます。
extend、append、exdef、apdef の引数として文字列または数値を渡すとバージョン指定として使用します。(作ってはみたものの使い所が微妙にわからない機能ですがw)
iname("Root.Node")
.extend({
Fuga: "fuga"
})
.extend("2.0", {
Fuga: "fugafuga"
})
.extend("1.0", {
Fuga: "fugahoge?"
});
console.log( (new Root.Node()).Fuga ); //fugafuga
サンプルではextendでFugaを3回設定しています。
実は名前空間には _ver_
というメンバーが自動で追加されています。デフォルトの値は -1 です。
・1回めは最初のFuga設定なのでそのまま入ります。_ver_
は -1 のままです。
・2回めはバージョン情報として "2.0" を指定しています。バージョン比較は型判定なしの比較演算子で、この場合 "2.0" > -1 がtrueとなり、Fugaに新たな値が設定されます。_ver_
は "2.0" になります。
・3回めはバージョン情報 "1.0" でFugaを設定しようとしています。しかし _ver_
の値は "2.0" ですので、"1.0" > "2.0" はfalseとなり、Fugaの値はそのままになります。_ver_
の値も変わりません。
#無名関数にinameを継承する
(2017/11/8) iname version 1.6.0
inameの第一引数にfunctionを渡すと extend、append、exdef、apdef を継承するfunctionを返します。
これにより無名関数のprototype構築が簡単に記述できるようになります。
var fn = iname(function(){}); //inameの機能を継承した無名関数を作り fn に参照を代入する
var fn = iname(); //引数なしで実行しても同じ結果になります
//extendを使ってprototypeメンバーを定義
fn.extend({
piyo: "piyopiyo",
hogefuga: function(){
console.log( this.piyo );
}
});
(new fn()).hogefuga(); //piyopiyo
この機能を使用することで名前空間のprototypeメンバーにクラス(的なfunction)を定義することが容易になりました。
具体的にはメソッドチェーンを利用した書き方となります。
iname("Root")
.extend({
Node: iname(function(){})
.extend({
piyo: "piyopiyo",
hogefuga: function(){
console.log( this.piyo ); //thisはRoot.prototype.Nodeのインスタンス
}
}),
Node2: function(){
(new this.Node).hogefuga(); //thisはRootのインスタンス
}
});
(new Root).Node2(); //piyopiyo
#補足
inameを利用したコードをClosure CompilerのAdvancedモードでminifyする場合はgitリポジトリにiname.externs.jsがありますのでこれを使うと捗ると思います。
以上です。
自分では使い倒しているライブラリなのですが、いかがでしょう。
kintoneプラグイン『機能拡張スタンダード All-In』をリリースしました。
よろしくお願いします。