JavaScriptでクラス継承する名前空間を作る

タイトルの通りの内容で、ネタとしてはすでに枯れきった感のあるテーマですが、ここでは「シンプル」ということを第一義として作ってみました。
<b>iname</b> という名前で<b>github</b>に公開しています。
<b>[[javascript] 名前空間とクラス継承 - iname](https://github.com/spica-git/iname)</b>
ここではコードの解説ではなく使い方についてまとめていきます。

また、同じことはいろんな方がやっておられます。
[[JavaScript] getter/setterも使えるエコ楽なクラス定義 - もちろん継承も - private変数も](http://qiita.com/LightSpeedC/items/3946088b58925234cc48)

---
#<b>機能概要</b>

<b>iname、extend、append、exdef、apdef</b>、という5つの関数で以下の機能が構成されています。

+ <b>グローバル（windowオブジェクト直下）に公開されるオブジェクトはひとつだけとし、この公開オブジェクトを名前空間とする</b>

名前空間の大前提ですね。
この<b>名前空間オブジェクトの型はfunction</b>で、<b>iname</b>を使って作ります。以降は単に<b>名前空間</b>と記述します。

+ <b>名前空間はクラス実装のための機能を継承している</b>

<b>extend、append、exdef、apdef</b>、という4つの関数が使えるようになります。これらを使って名前空間とそのprototypeにプロパティを追加します。

+ <b>名前空間のインスタンスは上位階層とプロトタイプチェーンがリンクしている</b>

サンプルで詳しく説明していきます。

主な機能の概要としては以上の3点です。
それ以外には <b>`_super_`</b> というプロパティが名前空間に追加されて、上位階層を参照します。

ではサンプルです。

---

#<b>使いかた</b>

まず最初に <b>iname</b> で名前空間を作ります。

```js:1-1.はじめての名前空間
iname("Root");
```
<b>iname</b>はこのスクリプトがglobal公開するfunctionです。全ての機能は<b>iname</b>から提供されます。
実行の結果、window下に <b>Root</b> という名前空間が追加されます。

```js:1-2.名前空間を階層にしてみるよ
iname("Root.Node"); //iname("Root")を実行していなくても実行可能

console.log( typeof window.Root );   //function
console.log( typeof window.Root.Node );   //function
```

名前空間の区切り文字はピリオドです。階層をひとつづつ順番に作る必要はありません。
次に、作られた名前空間をクラスと見立ててプロパティを追加していきます。

```js:1-3.プロパティの追加
Root.extend({
    Hoge: "hoge"
});

Root.Node.extend({
    Fuga: function(){ return "fuga"; }
});
```

ふたつ目の機能 <b>extend</b> です。
Root および Root.Node の型はfunctionなので、prototypeを持ちます。
<b>extend</b>は引数に指定されたオブジェクトのメンバーをprototypeに追加します。
<b>iname</b>で作られた名前空間は（途中経路も含めて）全て<b>extend</b>が使えます。

```js:1-4.プロパティの値を検証
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メンバーにもアクセスすることができます。

ここまでに使用した <b>iname</b> と <b>extend</b> で重要な機能はほぼ全てです。
これで十分という場合はこれ以降は読み進めなくても差し支えありません。お疲れ様でした。

---

#<b>名前空間にメンバーを追加する</b>

<b>iname</b>で作られた名前空間は<b>extend</b>が使えるようになっていました。同様に、<b>append、exdef、apdef</b>、も使えるようになっています。
それぞれの機能をまとめて列挙します。

+ <b>iname</b> …… 名前空間の作成
+ <b>extend</b> …… prototypeにプロパティを追加
+ <b>append</b> …… functionメンバーにプロパティを追加
+ <b>exdef</b> …… prototypeにdefinePropertyを使用してプロパティを追加
+ <b>apdef</b> …… functionメンバーにdefinePropertyを使用してプロパティを追加

以上です。わかる人はこれだけで何するかなんとなくわかるのではないでしょうか。

では、サンプル。

```js:2-1.appendを使ってみる
iname("Root.Node").append({
    Piyo: "piyo"
});

console.log( Root.Node.Piyo );  //piyo
```

<b>append</b>はprototypeではなく名前空間そのもののメンバーにプロパティを追加します。そのまんまですね……。
副作用は、後述するバージョン情報を使用する場合以外には特に無いので、わざわざappendを使わず直接メンバーに代入しても結果は同じです。
このサンプルでは <b>iname</b> の戻り値から直接 <b>append</b> を使っています。
戻り値は作成された名前空間ですので、メソッドチェーン的な書き方ができるというわけです。<b>iname</b>だけでなく、<b>extend、append、exdef、apdef</b>、すべて名前空間を返します。

---

#<b>definePropertyを使ってメンバーを追加する</b>


<b>exdef</b> と <b>apdef</b> はそれぞれ <b>extend</b> と <b>append</b> の亜種です。

```js:2-2.exdefとapdefを同時に使ってみる
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](https://msdn.microsoft.com/ja-jp/library/dd548687.aspx)

---

#<b>名前空間のコンストラクタ</b>
(2016/12/6 編集)

inameの<b>第二引数</b>にfunctionを渡すことで、その名前空間のコンストラクタにすることができます。

```js:3-1.名前空間のコンストラクタ
iname("Root.Node", function(){
    console.log( "Root.Nodeのインスタンスです" );
});

new Root.Node();  //Root.Nodeのインスタンスです
```

名前空間のインスタンスはスーパークラスを継承しますので <b>instanceof</b> で<b>true</b>がとれます。

```js:3-2.instanceofの結果
console.log( (new Root.Node()) instanceof Root.Node ); //true
console.log( (new Root.Node()) instanceof Root );  //true
```
---
#<b>メンバー定義のバージョン管理</b>
(2016/12/6 編集)

メンバーの定義名が衝突した際の制御としてバージョン名を個別に設定することができます。
<b>extend、append、exdef、apdef</b> の引数として文字列または数値を渡すとバージョン指定として使用します。（作ってはみたものの使い所が微妙にわからない機能ですがｗ）

```js:4-1.extendのバージョン指定
iname("Root.Node")
.extend({
    Fuga: "fuga"
})
.extend("2.0", {
    Fuga: "fugafuga"
})
.extend("1.0", {
    Fuga: "fugahoge?"
});

console.log( (new Root.Node()).Fuga );  //fugafuga
```

サンプルでは<b>extend</b>でFugaを3回設定しています。
実は名前空間には <b>`_ver_`</b> というメンバーが自動で追加されています。デフォルトの値は -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_`の値も変わりません。

---

#<b>無名関数にinameを継承する</b>
(2017/11/8) iname version 1.6.0

inameの<b>第一引数</b>にfunctionを渡すと <b>extend、append、exdef、apdef</b> を継承するfunctionを返します。
これにより<b>無名関数のprototype構築</b>が簡単に記述できるようになります。

```js:5-1.無名関数でinameの機能を使って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）を定義することが容易になりました。
具体的にはメソッドチェーンを利用した書き方となります。

```js:5-2.名前空間のprototypeにクラスを定義する
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リポジトリ](https://github.com/spica-git/iname)にiname.externs.jsがありますのでこれを使うと捗ると思います。

---

以上です。

自分では使い倒しているライブラリなのですが、いかがでしょう。

[kintone](https://kintone.cybozu.com/jp/)プラグイン[『機能拡張スタンダード All-In』](https://spica.tokyo/bluelaunch)をリリースしました。
よろしくお願いします。