対象
var original = {a: 1};
original.b = original; // 自己参照
既存の方法
いくつか既存の方法を試してみましたが、すべて思い通りにはなりませんでした。
うかつに実行すると例外が発生してしまいます
JSON.parse
var cloned = JSON.parse(JSON.stringify(original));
TypeError: Converting circular structure to JSON
が発生
jQuery
var cloned = $.extend(true, {}, original);
RangeError: Maximum call stack size exceeded
が発生
Coffeescript cookbook
RangeError: Maximum call stack size exceeded
が発生
underscore
var cloned = _.clone(original);
console.log(cloned.b === original.b); //(true) シャローコピー
今回作成したもの
だれかが既に解決していたら、ただ見つけられなかっただけです。
とりあえず作ってみたので公開します
var clone = (function() {
function cp (val) {
if (val === null ||
val === void 0 ||
typeof val === 'string' ||
typeof val === 'number' ||
typeof val === 'boolean' ||
typeof val === 'function') {
return val;
}
var constructor = Object.getPrototypeOf(val).constructor;
if (constructor === RegExp) {
var att = String(val).slice(val.source.length + 2);
return new RegExp(val.source, att);
}
if (constructor === Date) {
return new Date(val.getTime());
}
var self = this;
// 自己参照対策
var idx = self.obj1.indexOf(val);
if (~idx) {
return self.obj2[idx];
}
var rtn = constructor === Array ? [] : {};
this.obj1.push(val);
this.obj2.push(rtn);
if (constructor === Array) {
val.forEach(function(x) {
rtn.push(self.cp(x));
});
} else {
Object.keys(val).forEach(function(x){
rtn[x] = self.cp(val[x]);
});
}
return rtn;
}
function clone (val) {
var self = {
cp: cp,
obj1: [], // 自己参照対策
obj2: []
};
return self.cp(val);
}
return clone;
})();
検証
var cloned = clone(original);
console.log(cloned === original); // false
console.log(cloned.b === original.b); // false
console.log(original.b === original); // true
console.log(cloned.b === cloned); // true
console.log(cloned.b.b.b.b.b.a === original.b.b.a); // true
参照はそれぞれ自分自身への循環参照でディープコピーされています。
さいごに
対応しているのは、プリミティブといくつかの基本的なオブジェクトだけ。
DOMとかは対象外です。
関数だけは参照渡しになっているのでそこだけディープコピーじゃないけど、まあいいかと。
最初のプリミティブらへんの解決はかんたんに、
val === null || typeof val !== 'object'
とかでもいいかもしれません