0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScript のクラス定義を他のオブジェクト指向言語っぽく僕なりにアレンジしてみた

0
Last updated at Posted at 2016-04-20

もう ECMAScript の規格でクラス定義の文法が進んでるのでこんなまどろっこしいことはしなくてもよくなりそうですが(
まあそれが普及するまでのやり方の一つとして。

解説

defineClassというクラス定義用の関数を作りました。(文末に添付)
…こんなことするくらいならもう GitHub 作っちゃえよ、とも思いますが(
ちょっとアカウント管理の仕方に問題があってリポが作れずにいます。
defineClassにスーパークラスと、クラス定義をまとめたオブジェクト(連想配列)を渡すと
defineClass関数の中で Object.defineProperties 用のディスクリプターに整形してコンストラクター用の関数に設定してくれるというものです。

使用例

例えば先日投稿した Modified-UTF7 のエンコーダーならこんな感じで定義します。

modifiedUTF7.js
defineClass(null, {
    '': MUTF7 = function(){},
    'encode': ['static function', function(plain_str) {
        return plain_str
            .replace(/&/g, '&-')
            .replace(/[\0- \u007f-\uffff]+/g, function(str) {
                return '&' + Base64.encode(str) + '-';
            });
    }],
    'decode': ['static function', function(utf7_str) {
        return utf7_str
            .replace(/&([A-Za-z0-9\+\/]+=*)-/g, function(str, $1) {
                return Base64.decode($1);
            }).replace(/&-/g, '&');
    }]
});

defineClass(null, { の最初の null はスーパークラスです。
今回は何も継承しないので null にしてますが、
例えば Foo というクラスを継承したい場合は (Foo.prototype ではなく) Foo を渡します。
次の '': MUTF7 = function(){}, はコンストラクター定義です。
'' (空文字列) というキーに関数を入れると、それを defineClass 関数がコンストラクターとみなして、こいつに対して Object.defineProperties します。
わかりにくいですが、同時に MUTF7 という変数にコンストラクターを代入してるので、定義してるクラスの名前は MUTF7 になります。
'encode': ['static function', function(plain_str) の辺りは関数名と修飾子です。
プロパティはざっくり以下の文法で定義します。関数の場合は値のところに関数オブジェクトを入れます。

{
    'プロパティ名': ['修飾子', ]
}

ここでは encode という名前の静的関数を定義してます。 decode 関数も同様です。

getter/setter の定義例

BitQueue.js
defineClass(null, {
    '': BitQueue = function(){},
    'value_': ['hidden', 0],
    'length_': ['hidden', 0],
    'length': ['property', {
        'get': function() {
            return this.length_;
        }
    }],
    /* 中略 */
});

こっちのクラスには value_, length_ という隠しプロパティと length という getter が定義されてます。
プロパティは以下のような感じで、修飾子に property を入れて値のところに geter/setter を入れます。片方だけもおk。

{
    'プロパティ名': ['property', {
        'get': function() {
            /* getter */
        },
        'set': function(value) {
            /* setter */
        }
    }]
}

修飾子一覧

修飾子は以下からいくらでも選んでスペースで区切るか、配列に入れます。

修飾子 説明
static 静的
hidden (別名: private) for ... in 文で列挙されない
const (別名: final, readonly) 読み取り専用
property (別名: prop) プロパティ (値のところに getter/setter を定義)
function (別名: func, method) メソッド (※hidden const と同義)

property を使う場合 const は無視されます。

ソースコード (defClass.js)

defClass.js
//---------------------------------------------------------------------------
// ● クラス定義
//     superClass : (function) スーパークラス
//     members    : (object)   メンバー
//     {
//         // コンストラクター (名前が空文字列)
//         '': function(/*引数*/) { /*...*/ }
//         // フィールド
//         '名前': ['', 初期値]
//         // フィールド (初期値なし)
//         '名前': ['', undefined]
//         // 静的フィールド (クラス名.フィールド名 でアクセス)
//         '名前': ['static', 初期値]
//         // 定数 (変更できない; static 併用可)
//         '名前': ['const', 値]  //<- final や readonly でもおk
//         // hiddenメンバー (for...in 等で列挙されない; static,const 併用可)
//         // ※列挙されないだけで参照は外からもできます
//         '名前': ['hidden', 初期値]  //<- private でもおk
//         // 関数 (hidden const のエイリアス)
//         '名前': ['method', function(/*引数*/) { /*...*/ }]
//         // プロパティ (static,const,hidden 併用可)
//         '名前': ['property', {'get': function(){}, 'set': function(value){}}]
//     }
//---------------------------------------------------------------------------
function defineClass(superClass, members) {
    // コンストラクター
    var theClass = members[''] || function(){};
    delete members[''];

    // メンバー
    var descriptors = {};
    var staticDescriptors = {};
    for(var memberName in members) {
        var memberDefinition = members[memberName];
        var info = memberDefinition[0];

        // 修飾子定義を文字列の配列に分割
        if(typeof info === 'string') {
            info = info.trim();
            info = info !== '' ? info.split(/\s+/) : [];
        }

        // 修飾子の処理
        var isStatic = false;       // 静的
        var isConst = false;        // 読み取り専用
        var isEnumerable = true;    // 列挙可
        var isProperty = false;     // プロパティか否か
        for(var i = 0; i < info.length; ++i) {
            var def = info[i];

            if(def === 'static') {
                isStatic = true;
            }
            else if(def === 'const' ||
                    def === 'final' ||
                    def === 'readonly') {
                isConst = true;
            }
            else if(def === 'hidden' ||
                    def === 'private') {
                isEnumerable = false;
            }
            else if(def === 'prop' ||
                    def === 'property') {
                isProperty = true;
            }
            else if(def === 'func' ||
                    def === 'function' ||
                    def === 'method') {
                isConst = true;
                isEnumerable = false;
            }
            else if(def) {
                throw new RangeError('Invalid modifier: ' + def);
            }
        }

        // プロパティ定義まとめ
        var descriptor = {
            'configurable': false,
            'enumerable': isEnumerable
        };
        if(isProperty) {
            descriptor['get'] = memberDefinition[1]['get'];
            descriptor['set'] = memberDefinition[1]['set'];
        }
        else {
            descriptor['writable'] = !isConst;
            descriptor['value'] = memberDefinition[1];
        }

        // 追加
        (isStatic ? staticDescriptors : descriptors)[memberName] =
            descriptor;
    }

    // 継承
    if(superClass) {
        theClass.prototype.prototype = superClass.prototype;
    }

    // プロパティを適用
    Object.defineProperties(theClass, staticDescriptors);
    Object.defineProperties(theClass.prototype, descriptors);

    return theClass;
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?