installproperty.jsというものを書きました。

What?

要するにUndo可能なObject.definePropertyです。

var O = Object; /* to save space */
var o = {name:"dankogai", lang:"javascript"};
log( O.installProperty(o, 'lang', {value:"perl"}) );
log( o );
O.defineProperty(o, 'name', {writable:false});
log( O.installProperty(o, 'name', {value:"yusukebe"}) );
O.installProperty(o, 'lang', {value:"php"});
log( o );
Object.installProperty(o, 'ver', {value:5, enumerable:true});
log( o );
O.installProperties(o, {
    lang:   {value:"ruby"},
    ver:    {value:2.0}
});
log( o );
log( O.getOwnPropertyNames(o) );
log( O.revertProperty(o, 'lang') );
log( o );
log( O.revertProperties(o) );
log( o );
log( O.restoreProperties(o) );
log( o );
log( O.getOwnPropertyNames(o) );

自分自身もinstallProperty()でインストールしているので、 Object.restoreProperties(Object) できれいさっぱりアンインストール可能です。es2piにも統合済み。

So What?

こういうことがしやすくなります。

function myContext(fun){
    /* unfortunately getters|setters don't work */
    /* -- they're unconditionally immutable */
    Object.installProperties(Number.prototype, {
        double:{ value:function() { return 2*this } },
        triple:{ value:function() { return 3*this } },
    });
    fun();
    /* do not restoreProperties() since others may have extended Number */
    Object.revertProperties(Number.prototype);
};

myContext(function(){
    log((42).double());
    log((42).triple());
});
try {
    log((42).double());
    log((42).triple());
} catch(e) {
    log(e.message);
};

要するに、一時的にグローバルオブジェクト、特にプロトタイプを拡張するということがずっと容易になるわけです。最後に Object.revertProperties() しなければならない分、Perl の local Ruby 2.0 の refinement よりはめんどくさいのですが、これもライブラリー化できますし、していく予定です。

もちろん、今までだってやろうと思えばできました。

function myContext(fun){
    /* I don't care if they already exist */
    Number.prototype.double = function() { return 2*this };
    Number.prototype.triple = function() { return 3*this };
    fun();
    delete Number.prototype.double;
    delete Number.prototype.triple;
};

myContext(function(){
    log((42).double());
    log((42).triple());
});
try {
    log((42).double());
    log((42).triple());
} catch(e) {
    log(e.message);
};

しかしこれだと何を拡張したのかを覚えておかなければなりませんし、仮に .double.tripleがすでに存在していたら、きちんと元に戻さねばなりません(a la (jQuery|_).noConflict())。

Back to Prototype?

メンテナブルJavaScript - 11.2.3 メソッドを新規追加しない
Prototype の失敗から学びましょう。JavaScriptが将来どのように変化するのか、正確な予想はできません。JavaScript 標準の進化にあわせて、Prototypeのような JavaScript ライブラリから次世代の機能を決める手がかりを得てきました。実際、ネイティブの Array.prototype.forEach() メソッドはECMAScript 5で定義されています。これは Prototype の each() メソッドとかなり似た動作をします。問題は、公式の機能が元の機能とどのよう違っているのか、知られていない点にあります。ほんの些細な違いでも大きな問題が発生することがあります。

ご高説、ごもっとも。この場を借りて献本御礼。おかげで今や JavaScript におけるビルトインオブジェクトのプロトタイプ拡張って、今や近親相姦なみのタブーになったようにも感じます。

でもね。

「所有していないオブジェクトを変更しない」ってんだったら、ルートオブジェクト、つまりグローバル変数だってそうじゃね? $_ ならおkってNumberスタンダード、もといダブルスタンダードじゃないの?「プラグインを書け」っていうなら、ライブラリーではなくJavaScriptそのものへの「プラグイン」を書いてはいけない道理はないよね?

というわけで、模索はまだまだ続きます。

Enjoy!

Dan the JavaScripter with Too Many Objects to Extend

P.S.

Rails Hub情報局: 「20年後も現役プログラマでいたい」、まつもと氏がRuby20周年で語る
人類のためにJavaScriptは何とかしたほうがいい

それをJavaScriptできるか、それが問題。