Javascript 具有循环引用的深度克隆对象
Javascript Deep Clone Object with Circular References
我从 Dmitriy Pichugin 的 existing answer 中复制了下面的函数。这个函数可以在没有任何循环引用的情况下深度克隆一个对象 - 它有效。
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;
您可以将引用和结果存储在单独的数组中,当您找到具有相同引用的 属性 时,您只需要 return 缓存的结果。
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);
}
I removed the map and some of the other type comparisons to make it more readable.
如果您可以针对现代浏览器,请检查@trincot 可靠的 ES6 答案。
我建议使用 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
因为对象克隆有很多陷阱(循环引用、原型链、Set/Map 等)
我建议您使用一种经过充分测试的流行解决方案。
const cloneDeep = src => {
const clones = new WeakMap()
return (function baseClone(src) {
if (src !== Object(src)) {
return src
}
if (clones.has(src)) {
return clones.get(src)
}
const clone = {}
clones.set(src, clone)
Object.entries(src).forEach(([k, v]) => (clone[k] = baseClone(v)))
return clone
})(src)
}
const a = { x: 1 }
a.y = a
console.log(cloneDeep(a)) // <ref *1> { x: 1, y: [Circular *1] }
基本思路是您要避免出现“超出最大调用堆栈”的情况。
为了做到这一点,当需要克隆一个对象时,您需要先创建一个空对象,缓存它(使用 WeakMap
之类的东西),然后更新缓存的属性目的。如果您在创建副本后尝试缓存它,则在再次递归引用它时它不会被缓存(因此调用堆栈错误)。
您想做这样的事情:
function deepClone(value, cache = new WeakMap()) {
if(cache.has(value)) {
return cache.get(value)
}
if(isPrimitive(value) || isFunction(value)) {
return value;
}
if(value instanceof AnyParticularClass) {
// handle specially in any cases you plan to support
}
if(isObject(value)) {
const result = {}
cache.set(value, result);
Object.entries(value).forEach(function([key, val]) {
result[key] = deepClone(val, cache);
})
return result
}
if(Array.isArray(value)) {
return value.map(item => cloneDeep(item, cache));
}
}
此外,克隆一个函数没有任何意义,所以只 return 退出。 类 的处理实例将需要处理
我从 Dmitriy Pichugin 的 existing answer 中复制了下面的函数。这个函数可以在没有任何循环引用的情况下深度克隆一个对象 - 它有效。
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;
您可以将引用和结果存储在单独的数组中,当您找到具有相同引用的 属性 时,您只需要 return 缓存的结果。
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);
}
I removed the map and some of the other type comparisons to make it more readable.
如果您可以针对现代浏览器,请检查@trincot 可靠的 ES6 答案。
我建议使用 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
因为对象克隆有很多陷阱(循环引用、原型链、Set/Map 等)
我建议您使用一种经过充分测试的流行解决方案。
const cloneDeep = src => {
const clones = new WeakMap()
return (function baseClone(src) {
if (src !== Object(src)) {
return src
}
if (clones.has(src)) {
return clones.get(src)
}
const clone = {}
clones.set(src, clone)
Object.entries(src).forEach(([k, v]) => (clone[k] = baseClone(v)))
return clone
})(src)
}
const a = { x: 1 }
a.y = a
console.log(cloneDeep(a)) // <ref *1> { x: 1, y: [Circular *1] }
基本思路是您要避免出现“超出最大调用堆栈”的情况。
为了做到这一点,当需要克隆一个对象时,您需要先创建一个空对象,缓存它(使用 WeakMap
之类的东西),然后更新缓存的属性目的。如果您在创建副本后尝试缓存它,则在再次递归引用它时它不会被缓存(因此调用堆栈错误)。
您想做这样的事情:
function deepClone(value, cache = new WeakMap()) {
if(cache.has(value)) {
return cache.get(value)
}
if(isPrimitive(value) || isFunction(value)) {
return value;
}
if(value instanceof AnyParticularClass) {
// handle specially in any cases you plan to support
}
if(isObject(value)) {
const result = {}
cache.set(value, result);
Object.entries(value).forEach(function([key, val]) {
result[key] = deepClone(val, cache);
})
return result
}
if(Array.isArray(value)) {
return value.map(item => cloneDeep(item, cache));
}
}
此外,克隆一个函数没有任何意义,所以只 return 退出。 类 的处理实例将需要处理