Dmitriy Pichuginによる 既存の回答 から以下の関数をコピーしました。この関数は、循環参照なしでオブジェクトをディープクローンできます-機能します。
function deepClone( obj ) {
if( !obj || true == obj ) //this also handles boolean as true and false
return obj;
var objType = typeof( obj );
if( "number" == objType || "string" == objType ) // add your immutables here
return obj;
var result = Array.isArray( obj ) ? [] : !obj.constructor ? {} : new obj.constructor();
if( obj instanceof Map )
for( var key of obj.keys() )
result.set( key, deepClone( obj.get( key ) ) );
for( var key in obj )
if( obj.hasOwnProperty( key ) )
result[key] = deepClone( obj[ key ] );
return result;
}
ただし、プログラムは無限にループし、循環参照が原因であることに気付きました。
循環参照の例:
function A() {}
function B() {}
var a = new A();
var b = new B();
a.b = b;
b.a = a;
Mapを使用して、コピー元のオブジェクトをコピー先のオブジェクトにマップすることをお勧めします。実際、Bergiの提案に従って WeakMap
を使用してしまいました。ソースオブジェクトがマップ内にあるときはいつでも、それ以上再帰するのではなく、対応するコピーが返されます。
同時に、元のdeepClone
コードのコードの一部をさらに最適化できます。
プリミティブ値をテストする最初の部分には小さな問題があります。それはnew Number(1)
をnew Number(2)
とは異なる方法で処理します。これは、最初のif
の_==
_が原因です。 _===
_に変更する必要があります。しかし、実際には、コードの最初の数行はこのテストと同等のように見えます:Object(obj) !== obj
いくつかのfor
ループをより機能的な式に書き直しました
これにはES6のサポートが必要です。
_function deepClone(obj, hash = new WeakMap()) {
// Do not try to clone primitives or functions
if (Object(obj) !== obj || obj instanceof Function) return obj;
if (hash.has(obj)) return hash.get(obj); // Cyclic reference
try { // Try to run constructor (without arguments, as we don't know them)
var result = new obj.constructor();
} catch(e) { // Constructor failed, create object without running the constructor
result = Object.create(Object.getPrototypeOf(obj));
}
// Optional: support for some standard constructors (extend as desired)
if (obj instanceof Map)
Array.from(obj, ([key, val]) => result.set(deepClone(key, hash),
deepClone(val, hash)) );
else if (obj instanceof Set)
Array.from(obj, (key) => result.add(deepClone(key, hash)) );
// Register in hash
hash.set(obj, result);
// Clone and assign enumerable own properties recursively
return Object.assign(result, ...Object.keys(obj).map (
key => ({ [key]: deepClone(obj[key], hash) }) ));
}
// Sample data
function A() {}
function B() {}
var a = new A();
var b = new B();
a.b = b;
b.a = a;
// Test it
var c = deepClone(a);
console.log('a' in c.b.a.b); // true
_
オブジェクトの複製には多くの落とし穴があるため(循環参照、プロトチェーン、セット/マップなど)
よくテストされた人気のあるソリューションの1つを使用することをお勧めします。
lodash's _.cloneDeep または 'clone' npm module のように。
参照と結果を別々の配列に格納でき、同じ参照を持つプロパティが見つかった場合は、キャッシュされた結果を返すだけで済みます。
function deepClone(o) {
var references = [];
var cachedResults = [];
function clone(obj) {
if (typeof obj !== 'object')
return obj;
var index = references.indexOf(obj);
if (index !== -1)
return cachedResults[index];
references.Push(obj);
var result = Array.isArray(obj) ? [] :
obj.constructor ? new obj.constructor() : {};
cachedResults.Push(result);
for (var key in obj)
if (obj.hasOwnProperty(key))
result[key] = clone(obj[key]);
return result;
}
return clone(o);
}
マップと他のタイプの比較のいくつかを削除して、読みやすくしました。
最新のブラウザをターゲットにできる場合は、@ trincotの確かなES6回答を確認してください。