2007-11-27

JavaScriptにおけるdeep clone

http://blog.livedoor.jp/dankogai/archives/50957890.html

まず、Object.prototypeにメソッドを生やしてしまうとfor inでキーを列挙するときにいちいちhasOwnPropertyを使わないといけなくなるので普通は使いません。影響が大きすぎるので、よっぽど変態的なライブラリじゃないと使わない。他のライブラリと組み合わせるとほぼ確実に問題が起きる。それから、deep_cloneが欲しい場合はJSONを作ってevalすればよい。パフォーマンスを気にする場合とか、浅いコピーが必要な場合はそれなりに工夫する必要があるけど。

で、JSONにするのにFirefoxだとtoSourceというのが使える。unevalというラッパーがあって、これだとnullでも平気。
http://subtech.g.hatena.ne.jp/cho45/20071125/1195964965

つまりFirefoxだったらeval(uneval(obj))でOK。
Object.deep_clone = function(obj){
    return (typeof uneval == "function") ? eval(uneval(obj)) : eval(Object.toJSON(obj));
}
// Object.toJSONは別途適当に実装。prototype.jsに入ってたりしますね。


見てのとおり、一つ嫌な問題があって、prototypeに割り当てているにも関わらず、for (k in object)でclone自身が見えてしまうのです。これって何とかならないのかなあ....__proto__プロパティを使う方法だと新しすぎる?


これはhasOwnPropertyを使う。prototypeチェーンを辿らないで、そのオブジェクト自身のプロパティとして存在している場合のみtrueを返す。
obj = {keyname: "hoge"};
"toString" in obj // true
"hasOwnProperty" in obj // true
obj.hasOwnProperty("keyname") // true
obj.hasOwnProperty("hasOwnProperty") // false


http://blog.livedoor.jp/dankogai/archives/50957994.html

これも同様、Object.prototypeにtoSourceを自前で定義してしまうと影響が大きすぎるので、使えない。

----
突っ込まれそうなところを補足

- 適切にtoSourceが設定されてれば、任意のクラスのオブジェクトでもeval,unevalでcloneを作れる。
- toJSONでtoSourceと同等の出力を期待するのは間違ってる気がするので、クロスブラウザでunevalを実装した方が良いかも。
- okuさんが書いてるけど、元のオブジェクトに影響を与えないで、一部のプロパティのみを書き換えたオブジェクトを作りたい、という要求を満たすにはまさにprototypeを使った継承が使えるので、他の言語と比べてdeep_cloneの必要性自体が薄い、気がする。(元のオブジェクトの変更が反映されちゃうから微妙か)

こんなの。
function clone(obj){
    var F = function(){};
    F.prototype = obj;
    return new F;
}


プリミティブ値を継承した場合に起こる問題を解消してあるのがこれ。
http://nanto.asablo.jp/blog/2006/10/18/566348

2007-11-17

Amazonのギフト券のコピペを楽にするGreasemonkeyスクリプト

500円を6枚とかで送られてもコピペするのが面倒くさいのでグリモン書いた。

http://la.ma.la/misc/userjs/amazon_gift.user.js