Wazly
Web Apps
headlines

Cloz

概要


Cloz(クロウズ)は、JavaScriptで扱うオブジェクトの堅牢性を高めるライブラリです。Cloz化したオブジェクトは、読み書きが制限されますが、一般的なオブジェクトやクラスの概念と同様、継承が可能です。

オブジェクトの管理


フロントJSアプリの規模が大きくなってくると、可読性や依存性を考慮に入れて、機能を切り分けることが必然的になってきます。切り分けの単位は、関数であったり、オブジェクトであったり、またファイルであったりするでしょう。

オブジェクトで機能を分割する場合、例えば次のような例が考えられます。

var settings = {
    username: 'KNJ',
    grade: 'premium',
};

var app = {
    permit: function(){ alert('OK, ' + settings.username); },
    warn: function(){ alert('NG, ' + settings.username); },
};

if (settings.grade === 'premium') {
    app.permit();
}
else {
    app.warn();
}

このように、usernamegradepermit()およびwarn()は、グローバル変数やグローバル関数にするよりも、オブジェクトのプロパティとしてしまった方が名前の衝突が起こりにくくなり、また一目で分類が理解できます。

分類というのは、すなわち名前空間です。

次の例を見てください。

var app = {};

app.name = 'appname';

app.model = {};
app.controller = {};
app.view = {};

app.view.render = function(html){ document.write(html); }

ここで、appapp.viewは分類上の構造を示していますが、app.nameapp.view.renderは具体的な変数や関数を指しています。

機能と構造とが複雑に入り組んだ状態で、何の保証もなく各オブジェクトに直接アクセスすることは、思わぬ弊害を生み出すことがあります。特に、名前空間の上書きは絶対に回避しなければなりません。

Clozは、機能を提供するオブジェクトをCloz化し、get()set()などの一部のメソッドしか提供しないようにします。それによって、機能を提供するオブジェクトと名前空間(構造)を提供するオブジェクトが切り離され、オブジェクト管理のルールが形式的なものになり、コード全体に統一性を持たせることが簡単になります。

基本的な使い方


Clozオブジェクトはcloz()を使って生成します。

var creature = cloz();

生成時にプロパティを持たせることもできます。

var creature = cloz({
    name: 'creature',
});

Clozオブジェクトを基に新たなClozオブジェクトを生成することができます。(継承)

var mammal = cloz(creature);

Clozオブジェクトを拡張する場合は、第2引数にプロパティを指定します。

var man = cloz(mammal, {
    name: 'man',
    speak: function(){},
});

プロパティの値を取得したり、メソッドを取得したりするにはcloz.get()を使います。

var miki = cloz(man, {
    name: 'Miki',
    hair: 'blond',
    languages: cloz(base, {
        ja: 'こんにちは。',
        en: 'Hi.',
    }),
    speak: function(language){
        return this.get('languages').get(language);
    },
});

console.log(miki.get('name')); // => "Miki"
console.log(miki.get('speak', 'ja')); // => "こんにちは。"

プロパティを上書きしたり、追加したりするにはcloz.set()を使います。

miki.set('hair', 'brown');
miki.set('laugh', function(){ return 'Haha.'; });

プロパティの値がClozオブジェクトの場合は、cloz.extend()を使います。

miki.extend('language', {
    ja: 'やあ。',
    zh: '你好。',
});

console.log(miki.get('speak', 'ja')); // => "やあ。" (overridden)
console.log(miki.get('speak', 'en')); // => "Hi." (preset)
console.log(miki.get('speak', 'zh')); // => "你好。" (new)

Clozの挙動


Clozはプロトタイプ継承をします。Cloz化の基となったオブジェクトが変更されると、それを継承したClozオブジェクトも影響を受けます。ただし、上書きされたプロパティは動的にはなりません。

var miki_clone = cloz(miki, {
    name: 'Cloned Miki',
});

console.log(miki_clone.get('name')); // => "Cloned Miki"
console.log(miki_clone.get('hair')); // => "brown"

miki.set('name', 'Original Miki');
miki.set('hair', 'black');

console.log(miki_clone.get('name')); // => "Cloned Miki" (referred to miki_clone)
console.log(miki_clone.get('hair')); // => "black" (referred to miki)

3重以上にネストされたClozオブジェクトは作るべきではありません。次のように予期せぬうちにClozオブジェクトが基のオブジェクトに影響を与える可能性があるためです。

var dog = cloz(mammal, {
    status: cloz(base, {
        feelings: cloz(base, {
            happy: '^o^',
        }),
    }),
});

var pochi = cloz(dog);
pochi.get('status').extend('feelings', {
    happy: '^w^',
});

// pochi's method affected dog's property
console.log(dog.get('status').get('feelings').get('happy')); // => ^w^

API


get(property [, arguments ])

プロパティの値を取得、またはメソッドを実行します。

gain(property [, value = null [, arguments ] ])

get()と似ていますが、こちらはデフォルトの値を渡すことができます。

var app = cloz({}, {
    platform: ['Android', 'iOS'],
});

var game = cloz(app);

console.log(game.gain('os', ['Windows Phone'])); // => Array [ "Windows Phone" ]
console.log(game.get('os')); // => Error: Cannot find property "os"

getAll()

すべてのプロパティと値をオブジェクトで返します。

set(property, value)

プロパティに値をセットします。

set(extension)

オブジェクト形式でプロパティと値をセットします。

extend(property, extension)

値となっているClozオブジェクトを拡張します。

extension._cloz(function)

Clozオブジェクトやそれを継承したClozオブジェクトが作られたときに関数を実行します。

// output "created!" twice
var parent = cloz({}, {
    _cloz: function(){
        console.log('created!');
    }
});
var child = cloz(parent);

extension.__cloz(function)

Clozオブジェクトが作られたときに関数を実行します。このメソッドは継承されません。

// output "created!" only once
var parent = cloz({}, {
    __cloz: function(){
        console.log('created!');
    }
});
var child = cloz(parent); // => no output

応用例