Edited at

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

More than 1 year has passed since last update.

タイトルの通りの内容で、ネタとしてはすでに枯れきった感のあるテーマですが、ここでは「シンプル」ということを第一義として作ってみました。

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 で名前空間を作ります。


1-1.はじめての名前空間

iname("Root");


inameはこのスクリプトがglobal公開するfunctionです。全ての機能はinameから提供されます。

実行の結果、window下に Root という名前空間が追加されます。


1-2.名前空間を階層にしてみるよ

iname("Root.Node"); //iname("Root")を実行していなくても実行可能

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


名前空間の区切り文字はピリオドです。階層をひとつづつ順番に作る必要はありません。

次に、作られた名前空間をクラスと見立ててプロパティを追加していきます。


1-3.プロパティの追加

Root.extend({

Hoge: "hoge"
});

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


ふたつ目の機能 extend です。

Root および Root.Node の型はfunctionなので、prototypeを持ちます。

extendは引数に指定されたオブジェクトのメンバーをprototypeに追加します。

inameで作られた名前空間は(途中経路も含めて)全てextendが使えます。


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メンバーにもアクセスすることができます。

ここまでに使用した inameextend で重要な機能はほぼ全てです。

これで十分という場合はこれ以降は読み進めなくても差し支えありません。お疲れ様でした。



名前空間にメンバーを追加する

inameで作られた名前空間はextendが使えるようになっていました。同様に、append、exdef、apdef、も使えるようになっています。

それぞれの機能をまとめて列挙します。



  • iname …… 名前空間の作成


  • extend …… prototypeにプロパティを追加


  • append …… functionメンバーにプロパティを追加


  • exdef …… prototypeにdefinePropertyを使用してプロパティを追加


  • apdef …… functionメンバーにdefinePropertyを使用してプロパティを追加

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

では、サンプル。


2-1.appendを使ってみる

iname("Root.Node").append({

Piyo: "piyo"
});

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


appendはprototypeではなく名前空間そのもののメンバーにプロパティを追加します。そのまんまですね……。

副作用は、後述するバージョン情報を使用する場合以外には特に無いので、わざわざappendを使わず直接メンバーに代入しても結果は同じです。

このサンプルでは iname の戻り値から直接 append を使っています。

戻り値は作成された名前空間ですので、メソッドチェーン的な書き方ができるというわけです。inameだけでなく、extend、append、exdef、apdef、すべて名前空間を返します。



definePropertyを使ってメンバーを追加する

exdefapdef はそれぞれ extendappend の亜種です。


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



名前空間のコンストラクタ

(2016/12/6 編集)

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


3-1.名前空間のコンストラクタ

iname("Root.Node", function(){

console.log( "Root.Nodeのインスタンスです" );
});

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


名前空間のインスタンスはスーパークラスを継承しますので instanceoftrueがとれます。


3-2.instanceofの結果

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)


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


サンプルでは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構築が簡単に記述できるようになります。


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)を定義することが容易になりました。

具体的にはメソッドチェーンを利用した書き方となります。


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リポジトリにiname.externs.jsがありますのでこれを使うと捗ると思います。


以上です。

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

kintoneプラグイン『機能拡張スタンダード All-In』をリリースしました。

よろしくお願いします。