带有循环引用的Javascript深度克隆对象
我从Dmitriy Pichugin的一篇文章中复制了下面的函数。此函数可以在没有任何循环引用的情况下深度克隆对象-它可以正常工作带有循环引用的Javascript深度克隆对象,javascript,node.js,clone,circular-reference,Javascript,Node.js,Clone,Circular Reference,我从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
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;
您可以将引用和结果存储在单独的数组中,当您找到具有相同引用的属性时,只需返回缓存的结果
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答案。我建议使用映射来映射源中的对象,并将其副本映射到目标中。事实上,我最终使用了Bergi的建议。只要源对象在映射中,就会返回其相应的副本,而不是进一步递归 同时,可以进一步优化原始
deepClone
代码中的一些代码:
- 原始值的第一部分测试有一个小问题:它将
与新编号(1)
区别对待。这是因为第一个新编号(2)
中的if
。应将其更改为=
。但实际上,前几行代码似乎相当于此测试:==
Object(obj)!=obj
- 我还将一些for循环重写为更多函数表达式
函数deepClone(obj,hash=new WeakMap()){
//不要尝试克隆原语或函数
if(Object(obj)!==obj | | obj instanceof Function)返回obj;
if(hash.has(obj))返回hash.get(obj);//循环引用
尝试{//尝试运行构造函数(不带参数,因为我们不知道参数)
var result=new obj.constructor();
}catch(e){//构造函数失败,在不运行构造函数的情况下创建对象
结果=Object.create(Object.getPrototypeOf(obj));
}
//可选:支持一些标准构造函数(根据需要扩展)
if(映射的obj实例)
Array.from(obj,([key,val])=>result.set(deepClone(key,hash),
deepClone(val,hash));
else if(集合的obj实例)
Array.from(obj,(key)=>result.add(deepClone(key,hash));
//在散列中注册
hash.set(obj,result);
//递归地克隆和分配可枚举的自身属性
返回Object.assign(结果,…Object.keys(obj).map(
key=>({[key]:deepClone(obj[key],hash)});
}
//样本数据
函数A(){}
函数B(){}
var a=新的a();
var b=新的b();
a、 b=b;
b、 a=a;
//测试一下
var c=深度克隆(a);
console.log(c.b.a.b中的“a”);//正确
因为对象克隆有很多陷阱(循环引用、原型链、集合/映射等)我建议您使用一种经过良好测试的流行解决方案 比如,或者。
const cloneDeep=src=>{
const clones=new WeakMap()
返回(函数baseClone(src){
如果(src!==对象(src)){
返回src
}
if(clones.has(src)){
返回克隆。获取(src)
}
const clone={}
clones.set(src,clone)
Object.entries(src.forEach(([k,v])=>(clone[k]=baseClone(v)))
返回克隆
})(src)
}
常数a={x:1}
a、 y=a
console.log(cloneDeep(a))/{x:1,y:[循环*1]}
基本思想是避免出现“超过最大调用堆栈”的情况
为了做到这一点,当需要克隆对象时,您需要首先创建一个空对象,缓存它(使用类似于WeakMap
)的东西),然后更新缓存对象上的属性。如果在创建副本后尝试对其进行缓存,则在再次递归引用该副本时,不会对其进行缓存(从而导致调用堆栈错误)
您希望执行以下操作:
函数deepClone(值,cache=new WeakMap()){
if(cache.has(value)){
返回cache.get(值)
}
if(isPrimitive(value)| | isFunction(value)){
返回值;
}
if(任何特定类的值实例){
//特别处理您计划支持的任何情况
}
if(等值对象(值)){
常量结果={}
cache.set(值、结果);
Object.entries(value).forEach(函数([key,val])){
结果[键]=深度克隆(val,缓存);
})
返回结果
}
if(数组.isArray(值)){
返回值.map(item=>cloneDeep(item,cache));
}
}
此外,克隆一个函数没有任何意义,所以只需返回该函数即可。需要处理类的实例您可以创建一个数组,用于存储每个“要克隆”对象的对象引用,并使用
.indexOf()
检查引用是否存储在该列表中。您可能想看看@MikeMcCaughan啊,有趣!泰!它看起来非常有用,但不保留自定义类实例。啊,我得到了异常uncaughtrangeError:超过了最大调用堆栈大小(…)
,这似乎是由于在deepClone
函数的第19行重复调用所致。我在问题中的示例a
和b
对象上使用了它。@Lolums我用更正检查更新了我的答案。请注意,为了使函数更易于理解,对其进行了一些精简:)这是一个非常适合WeakMap
的用例,而不是使用引用和缓存结果@贝基,这将是一个伟大的补充!谢谢,现在可以用了。几乎:deepClone(a)
返回a{b:b{a:undefined}}
如果我的一个对象构造函数期望接收参数,我相信这将失败<代码>函数A(foo){如果(!foo)抛出错误('pass foo!')}
@Artin,那么deepClone函数如何知道要传递给构造函数的参数?据我所知,JavaScript