で、き、た。
What?
なるべく多くの種類のオブジェクトを、なるべく直感的に、限りなく透明に近く wrap するためのシステムです。
こんな感じ。
var _ = Object.Wrap; /* for convenience */ try { log( _(42) .learn('square', function() { return this*this }) .square() * 1 /* 1764 */ ); log( (42).square() /* TypeError: Object 42 has no method 'square' */ ); }catch(e){ log(e.message); }; /* class method without changing Number */ _.Number.prototype .learn('times', function(f) { for (var i = 0; i < this; i++) f(i) }); try { _(4).times(function(n){ log(n) }); (4).times(function(n){ console.log(n) }); /* TypeError: Object 4 has no method 'times' */ }catch(e){ log(e.message); };
見ての通り、全てはObject.Wrap
の中にあり、それ以外のグローバルプロパティには指一本触れません。ここまでは Underscore.js なども同様ですが、見ての通りプリミティブ型もwrapできます。
es2piにイカんのイを表明しそうなメンテナブルJavaScriptの支持者も、これならヨいのヨを表明してくださるのではないでしょうか。
*/ 11.3.3 Facadeパターンファサードは、インターフェース越しに完全に制御できる点から、メンテナンス可能な JavaScript にうまく適しています。舞台裏にあるオブジェクトへのアクセスを効率よくフィルタリングすることで、そのオブジェクトのプロパティやメソッドに対するアクセスを許可および禁止できます。
ところで同書のサンプルコードが buggy なんだが…
P. 119DOMWrapper.prototype.addClass = function(className) { element.className += " " + className; /* where is this? */ };
Object.Wrap()
閑話休題。
実は関数としてのObject(o)
は、o
がプリミティブなら wrap されたオブジェクトを、そうでなければo
をそのまま返す wrapper function です。Object.Wrap(o)
もそれを規範としています。
Object(o) === o; /* true if o is already an object */ Object.Wrap(o) === o; /* true if o is already wrapped */
auto(un)?wrap
現時点でNumber
, String
, Array
, Object
は autowrap されます。
var assert = log; /* for convenience; */ var n = 0, wn = Object.Wrap(n), s = '', ws = Object.Wrap(s), o = {}, wo = Object.Wrap(o), a = [], wa = Object.Wrap(a); assert(wn !== n && wn.value === n); assert(ws !== s && ws.value === s); assert(wo !== n && wo.value === o); assert(wa !== n && wa.value === a);
コレクション型はとにかく、プリミティブ型もwrapできるところが特長ですが、しかし Null
, Undefined
, Boolean
はそのままでは wrap しません。理由は、 Boolean 演算子 が coerce しないから。
var _ = Object.Wrap; log( Object(21) + Object(21) ); /* 42 */ log( Object('4') + Object('2') ); /* '42' */ log( !!Object(false) ); /* surprisingly true */ log( _(21) + _(21) ); /* 42 */ log( _('4') + _('2') ); /* '42' */ log( !!_(false) ); /* natually false */ log( !!_(null) ); /* false */ log( !!_(undefined) ); /* false */ log( !!_(false, 1) ); /* true because it is wrapped */ log( !!_(false, 1).value ); /* false because it is unwrapped explicitly */
wrap by request
ただ前述の例を見てのとおり、第二引数にtruthyな値を入れるとwrapしてくれるようになります。
var assert = log; /* for convenience; */ var z = null, wz = Object.Wrap(z, 1), u = undefined, wu = Object.Wrap(u, 1), b = false, wb = Object.Wrap(b, 1), f = function(a){return a}, wf = Object.Wrap(f, 1), r = /^.*$/, wr = Object.Wrap(r, 1), d = new Date(0), wd = Object.Wrap(d, 1); assert(Object.Wrap(z) === z && wz !== z && wz.value === z); assert(Object.Wrap(u) === u && wu !== u && wu.value === u); assert(Object.Wrap(b) === b && wb !== b && wb.value === b); assert(Object.Wrap(f) === f && wf !== f && wf.value === f); assert(Object.Wrap(r) === r && wr !== r && wr.value === r); assert(Object.Wrap(d) === d && wd !== d && wd.value === d);
.learn()
そもそもwrapしたい理由は、ビルトインプロトタイプ、特にObject.prototype
を拡張したくない、というかするのが怖いからなのですが、だからといってwrapper用のprototypeを生で書くのは大変です。まず元の値をunwrapし、引数もunwrapし、実行してから結果をwrapしなおすというのは骨が折れます。
そのために.learn
があります。ここで普通に普通のオブジェクトのプロトタイプを拡張する時のように関数定義を書けば、あとはそれをwrapper用に変換してインストールしてくれます。
var _ = Object.Wrap; var wn = _(42); wn.learn('square', function() { return this*this }, 'Number'); log( wn.square() ); log( '' + wn.square );
もちろんメソッド単体ではなく、プロトタイプ全体も拡張できます。というかプロトタイプ自身が.learn()
できるので、こうできます。
var _ = Object.Wrap; _.Number.prototype.learn({ times:function(f) { for (var i = 0; i < this; i++) f(i) }, toThe:function(n) { return Math.pow(this,n) } }); _(16).times(function(n) { log( _(2).toThe(_(n)) ); });
実際プロトタイプにあらかじめ用意してあるメソッドは、ほとんどこの方法で実装しています。
.value
Object.Wrapでwrapしたオブジェクトは、見ての通りプリミティブであれば必要に応じて(つまり演算子適用のタイミングで)unwrapしてくれるすぐれものですが、その手はコレクション型には利きません。
とはいえ、.value
で簡単にunwrapできます。
var _ = Object.Wrap; log( _([0,1,2,3]).slice(1)[0] ); /* doesn't work */ log( _([0,1,2,3]).slice(1).value[0]); /* [1,2,3][0] => 1 */
実はgetterでvalueOf()
しているだけですが、getterのおかげで四文字節約できます。
[]の代わりに
コレクション型には.get
, .set
, .has
, .delete
も用意されています。
var _ = Object.Wrap; log( _([0,1,2,3]).has(1) ); /* true */ log( _([0,1,2,3]).has(4) ); /* false */ log( _({zero:0,one:1}).has('zero') ); /* true */ log( _({zero:0,one:1}).has('four') ); /* false */ log( _([0,1,2,3]).get(1).value ); /* 1 */ log( _([0,1,2,3]).get(4).value ); /* undefined */ log( _({zero:0,one:1}).get('zero').value); /* 0 */ log( _({zero:0,one:1}).get('four').value); /* undefined */ var wa = _([0,1,2,3]); log( wa.set(5, 5).value ); /* 5 is the return value */ log( wa.value ); /* [0,1,2,3,undefined,5] */ var wo = _({zero:0,one:1}); log( wo.set('five', 5).value ); /* 5 */ log( wo.value ); /* {zero:0,one:1,five:5} */ wa = _([0,1,2,3]); log( wa.delete(0) ); /* true */ log( wa.delete(4) ); /* false */ log( wa.value ); /* [undefined,1,2,3] */ wo = _({zero:0,one:1}); log( wo.delete('zero') ); /* true */ log( wo.delete('five') ); /* false */ log( wo.value ); /* {one:1} */
.methods
Object.Wrapの存在意義は、こういうことをしてドヤ顔をするためです:-)
var _ = Object.Wrap; log( _({zero:0,one:1,two:2,three:3}) .values() /* _([0,1,2,3]) */ .map(function(x){ return x * x }) /* _([0,1,4,9]) */ .filter(function(x){ return x % 2 === 0}) /* _([0,4]) */ .pop() /* _(4) */ * _(5) * _(2) + 2 /* 42 */ );
しかしこうなると、それぞれのオブジェクトが何が出来るかわかりにくくなりもします。
オブジェクト自身に答えていただきましょう。
var _ = Object.Wrap; log( _(null,1).methods ); log( _(undefined,1).methods ); log( _(false,1).methods ); log( _(0).methods ); log( _("").methods ); log( _([]).methods ); log( _({}).methods );
Rubyistのみなさん、おかえりなさい!。
さいごに
これを作るためにES5の機能を使いまくったのですが、どう使いまくったかはソースを参照いただくということで。
Enjoy -- without messing the environment!
Dan the JavaScripters with Too Many Objects to Wrap
このブログにコメントするにはログインが必要です。
さんログアウト
この記事には許可ユーザしかコメントができません。