2010年10月08日 06:30 [Edit]

javascript - making primitive types extendable

その方向性や、よし。

【連載】Extend.jsで楽しくJavaScriptプログラミング! - 第1回 Extend.jsのつかいかた (JavaScript,javascript,extend.js) - 株式会社あゆた
しかし、Extend.jsは以下のような特徴を備えています。
  • オブジェクト指向ライクな利用感、かつjQueryライクな使いやすさ
  • Web Workersの中でも安心して利用できる
  • 拡張機能も備えたフレームワークになっている

しかしこれでは萎えてしまう。

なんとか primitive types も extended な状態を保てないだろうか。


というわけで、やってみたのが以下。あくまで proof of concept なので、扱っているのは Boolean, Number and String の三種類だけ。

(function(global){

var ucfirst = function(s){ return s.charAt(0).toUpperCase() + s.substr(1) };

var _ = function(o){ return new _[ucfirst(typeof(o))](o) };

_.Number =  _.Boolean = function(o){ this.__value__ = o };
_.String = function(o){ this.__value__ = o; this.length = o.length };

(function(protos){
    for (var i = 0, p; p = protos[i]; i++) (function(p) {
        (function(methods){
            for (var i = 0, m; m = methods[i]; i++) (function(m){ 
                _[p].prototype[m] = function(){
                    var v = this.__value__;
                    return v[m].apply(v, arguments);
                };
            })(m);
        })(['toString', 'valueOf']);
    })(p);
})('Boolean Number String'.split(/\s+/));

(function(p){
    p.not = function(){ return _( ! this.valueOf() ) };
    p.and = function(x){ return _( this.valueOf() && !!(x.valueOf()) ) };
    p.or  = function(x){ return _( this.valueOf() || !!(x.valueOf()) ) };    
    p.xor = function(x){
        return _( !!(this.valueOf()) ? !(x.valueOf()) : !!(x.valueOf()) )
    };
})(_.Boolean.prototype);

(function(methods){
    for (var i = 0, m; m = methods[i]; i++) (function(m){ 
        if (Number.prototype[m]) _.Number.prototype[m] = function(){
            var v = this.__value__;
            return _( v[m].apply(v, arguments) );
        };
    })(m);
})(
    ('toExponential toFixed toLocaleString toPrecision toString').split(/\s+/)
);

(function(methods){
    for (var i = 0, m; m = methods[i]; i++) (function(m){ 
        if (String.prototype[m]) _.String.prototype[m] = function(){
            var v = this.__value__;
            return _( v[m].apply(v, arguments) );
        };
    })(m);
})(
    ('charAt charCodeAt concat indexOf lastIndexOf localeCompare match '
    +'replace search slice split substr substring toLocaleLowerCase '
    +'toLowerCase  toUpperCase trim trimLeft trimRight').split(/\s+/)
);

(function(methods){
    for (var i = 0, m; m = methods[i]; i++) (function(m){ 
        _.String.prototype[m] = function(){
            var args = [].slice.apply(arguments);
            args.unshift(this.__value__);
            return _( global[m].apply(null, args) );
        };
    })(m);
})(
    ('decodeURI decodeURIComponent encodeURI encodeURIComponent escape eval '
    +'parseInt parseFloat').split(/\s+/)
);

_.String.prototype.escapeHTML = function(){
    return _( this.__value__.replace(/[<>&]/g, function(m){
        return '&' + {'<':'lt','>':'gt','&':'amp'}[m] + ';'
    }));
};

(function(p){
    if (! p.trim) p.trim = function(){
        return _(this.__value__.replace(/^\s+/,'').replace(/\s+$/,''));
    };
    if (! p.trimRight) p.trimRight = function(){
        return _(this.__value__.replace(/\s+$/,''));
    };
    if (! p.trimLeft) p.trimLeft = function(){
        return _(this.__value__.replace(/^\s+/,''));
    };
})(_.String.prototype);

global._ = _;

})(this);

要するに、this.__value__に本来の値を隠し持っておき、各メソッドはその値を処理した後、再び_()する。valueOf()で本来の値を取り出すようにしておけば、そのまま+-*/したり文字列になったりする、使い勝手はほぼ組み込みプリミティブのままのオブジェクトの出来上がり。

Demo


見ての通り、Booleanだけは暗黙の型変換が効かない。明示的にvalueOf()しないと駄目。しかし一番使い出のありそうなNumberStringは、この方法で親オブジェクトを改変せずに拡張することが出来た。

暇な時にきちんと使えるライブラリーに仕上げようかな…

Dan the JavaScript Extender


この記事へのトラックバックURL