Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/node.js/37.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
带有循环引用的Javascript深度克隆对象_Javascript_Node.js_Clone_Circular Reference - Fatal编程技术网

带有循环引用的Javascript深度克隆对象

带有循环引用的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

我从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;

您可以将引用和结果存储在单独的数组中,当您找到具有相同引用的属性时,只需返回缓存的结果

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循环重写为更多函数表达式

这需要ES6支持:

函数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