2009年02月26日 11:30 [Edit]

javascript - uneval() for the rest of us!

うーん、それもそうなんだけど....

最速インターフェース研究会 :: JavaScriptにおけるdeep clone
まず、Object.prototypeにメソッドを生やしてしまうとfor inでキーを列挙するときにいちいちhasOwnPropertyを使わないといけなくなるので普通は使いません。
で、JSONにするのにFirefoxだとtoSourceというのが使える。unevalというラッパーがあって、これだとnullでも平気。

そのuneval()が他にないので、作ってみた。

初出2007.11.27; 追記2008.06.14, 2009.02.26

文字列のEscapeをFirefox互換に。あと、

にもあるので手を入れたい方は是非。


Source:
stdout:
stderr:

今度は、Built-in Objects には一切手を触れない方向で。

ソースはこちら。見ての通り、プロトタイプを使わないと、動作を切り替える際にlinear searchしなきゃいけなくて少し泥臭い(オブジェクトのIDを取れれば、それをキーにしてディスパッチテーブルを作ることもできるのだけどなあ....)。ちょっと工夫してあるのは、uneval_set(proto, name, func)でserializerをインストールできるようにしてあるところ。ユーザー定義オブジェクトでもserializerを登録することでserializeできる。

/lang/javascript/clone/trunk/uneval.js ? CodeRepos::Share ? Trac
/*
 * $Id: uneval.js,v 0.4 2011/12/15 03:06:51 dankogai Exp dankogai $
 *
 *  Licensed under the MIT license.
 *  http://www.opensource.org/licenses/mit-license.php
 */

(function(global){

if (typeof(global['uneval']) !== 'function') {
    var hasOwnProperty = Object.prototype.hasOwnProperty;
    var protos = [];
    var char2esc = {'\t':'t','\n':'n','\v':'v','\f':'f','\r':'\r',    
                    '\'':'\'','\"':'\"','\\':'\\'};
    var escapeChar = function(c){
        if (c in char2esc) return '\\' + char2esc[c];
        var ord = c.charCodeAt(0);
        return ord < 0x20   ? '\\x0' + ord.toString(16)
        :  ord < 0x7F   ? '\\'   + c
        :  ord < 0x100  ? '\\x'  + ord.toString(16)
        :  ord < 0x1000 ? '\\u0' + ord.toString(16)
        : '\\u'  + ord.toString(16)
    };
    var uneval_asis = function(o){ return o.toString() };
    /* predefine objects where typeof(o) != 'object' */
    var name2uneval = {
        'boolean':uneval_asis,
        'number': uneval_asis,
        'string': function(o){
            return '\''
            + o.toString()
               .replace(/[\x00-\x1F\'\"\\\u007F-\uFFFF]/g, escapeChar)
            + '\''
        },
        'undefined': function(o){ return 'undefined' },
        'function':uneval_asis
    };

    var uneval_default = function(o, np){
        var src = []; // a-ha!
        for (var p in o){
            if (!hasOwnProperty.call(o, p)) continue;
            src[src.length] = uneval(p)  + ':' + uneval(o[p], 1);
        };
        // parens needed to make eval() happy
        return np ? '{' + src.toString() + '}' : '({' + src.toString() + '})';
    };

    uneval_set = function(proto, name, func){
        protos[protos.length] = [ proto, name ];
        name2uneval[name] = func || uneval_default;
    };

    uneval_set(Array, 'array', function(o){
            var src = [];
            for (var i = 0, l = o.length; i < l; i++)
                src[i] = uneval(o[i]);
            return '[' + src.toString() + ']';
        });
    uneval_set(RegExp, 'regexp', uneval_asis);
    uneval_set(Date, 'date', function(o){
            return '(new Date(' + o.valueOf() + '))';
        });
    
    var typeName = function(o){
        // if (o === null) return 'null';
        var t = typeof o;
        if (t != 'object') return t;
        // we have to lenear-search. sigh.
        for (var i = 0, l = protos.length; i < l; i++){
            if (o instanceof  protos[i][0]) return protos[i][1];
        }
        return 'object';
    };
    
    uneval = function(o, np){
        // if (o.toSource) return o.toSource();
        if (o === null) return 'null';
        var func = name2uneval[typeName(o)] || uneval_default;
        return func(o, np);
    }
}

if (typeof(global['clone']) !== 'function') {
    clone = function(o){
        try{
            return eval(uneval(o));
        }catch(e){
            throw(e);
        }
    };
}

})(this);

Enjoy!

Dan the Javascripter

追記2009.02.26:少しだけ手直し。


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