当前位置: 首頁 > javascript > 关于JavaScript深拷贝的递归循环

关于JavaScript深拷贝的递归循环

前几天面试的时候,做考卷,遇到一个JS实现深拷贝的问题,说来惭愧,读题目都不知道是什么意思。

后来面试的时候,突然理解是什么意思,被要求写出来,脑袋里面想到的是使用FOR IN循环。

也成功写出来了,结构大概是这样。

        
        function P(obj){}
        P.prototype.caa=function(){console.log(1)};
        var p =new RegExp('/[.]/');
        var obj1;
        obj1=new P();
        var newObj;
        obj1={a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:Date.now(),e:p}}}};//一个多层嵌套对象
        function objCopy(obj){
            var copy_obj={};
            for(var i in obj){
                if(typeof obj[i]=='object'){//如果对应KEY的数据类型是object的话,就进行递归调用
                      copy_obj[i]=objCopy(obj[i]);
                }else{
                      copy_obj[i]=obj[i];
                }
            }
            return copy_obj;
        }
       var newObj=objCopy(obj1);
        console.log(obj1);
        console.log(newObj);

运行结果如下


可以首先发现一个问题,就是正则对象是没有被COPY的,这是当时被问到可能出现什么错误的情况的时候我没有答出来的。


JS的一些特殊对象,有些结构并不与一般对象结构相同,需要用其他特殊的方式进行特别处理,正则对象就是(另外,如果是一个构造函数所生成的对象,也是可以拷贝到该构造函数的方法的)。


这篇文章主要要讲另外一个造成的可能出错问题,就是当对象相互循环循环引用的时候,会出现的错误(Maximum call stack size exceeded)。


代码如下

        function P(obj){
        }
        P.prototype.caa=function(){console.log(1)};
        var p =new RegExp('/[.]/');
        var obj1,obj2;
        obj1=new P();
        obj1={a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:Date.now(),e:p}}}};
        obj2={a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:Date.now(),e:p}}}};
        obj1.f=obj2;
        obj2.f=obj1;
        function objCopy(obj){
            var copy_obj={};
            var index=0;
            for(var i in obj){
                if(typeof obj[i]=='object'){//如果对应KEY的数据类型是object的话,就进行递归调用
                        copy_obj[i]=objCopy(obj[i]);
                }else{
                        copy_obj[i]=obj[i];
                }
            }
            return copy_obj;
        }
        var newObj=objCopy(obj1);
        console.log(obj1);
        console.log(newObj);

jQuery的copy方法也会出现一样的错误,因为使用的代码原理是一样的。

我想了一下如何可以处理掉这个出错,包括网上查看资料或者其它同行发布的blog。

然而没有找到讲解如何处理这个深拷贝过多调用的问题,后来自己突然想到,是不是在循环引用的这个地方,在刚刚进入被拷贝对象的时候,给一个MARK,然后如果第二次要循环这个对象的时候(即可能陷入无限循环的时候)只给予这个对象赋予浅拷贝(直接赋值,类似指针变量的效果),不在递归,这样可以提高效率(不用再次递归,并且解决了过多回调的问题)


代码如下

        function P(obj){
        }
        P.prototype.caa=function(){console.log(1)};
        var p =new RegExp('/[.]/');
        var obj1,obj2;
        obj1=new P();

        obj1={a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:Date.now(),e:p}}}};
        obj2={a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:Date.now(),e:p}}}};
        obj1.f=obj2;
        obj2.f=obj1;
        function objCopy(obj){
            var copy_obj={};
            var index=0;
            for(var i in obj){
                if(index==0){
                    obj.copyKey=1;
                }

                if(typeof obj[i]=='object'){//如果对应KEY的数据类型是object的话,就进行递归调用
                    if(!obj[i].copyKey){
                        copy_obj[i]=objCopy(obj[i]);
                        delete copy_obj[i].copyKey;
                        delete obj[i].copyKey;
                    }else{
                        copy_obj[i]=obj[i];
                        delete copy_obj[i].copyKey;
                        delete obj[i].copyKey;
                    }
                }else{
                    copy_obj[i]=obj[i];
                }

                index++;
            }
            return copy_obj;
        }
        var newObj=objCopy(obj1);
        console.log(obj1)
        console.log(obj2)
        console.log(newObj)


可以发现,过多调用的出错信息不见了,并且展开这个对象的时候,引用是正确的 并且可以无限展开。


然后就在我打这篇文章的同时,我又忽然想到,如果多次引用,因为这个copyKey的mark,可能在第一次调用的时候被清除了,第二次调用的时候还是会陷入循环引用,产生循环引用的错误,测试了一下,和猜测的一样,于是我又改了一下代码。

        function P(obj){
        }
        P.prototype.caa=function(){console.log(1)};
        var p =new RegExp('/[.]/');
        var obj1,obj2;
        obj1=new P();

        obj1={a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:Date.now(),e:p}}}};
        obj2={a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:Date.now(),e:p}}}};
        obj1.f=obj2;
        obj2.f=obj1;
        obj1.a=obj2;
        obj2.a=obj1;
        function objCopy(obj,key){
            var copy_obj={};
            var index=0;
            for(var i in obj){
                if(index==0){//添加copyMark
                    obj.copyKey=1;
                }
                if(typeof obj[i]=='object'){//如果对应KEY的数据类型是object的话,就进行递归调用
                    if(!obj[i].copyKey&&key!=i){
                        copy_obj[i]=objCopy(obj[i],i);//递归调用增加对对象KEY的判断
                        delete copy_obj[i].copyKey;//去除copyMark,来自递归调用
                        delete obj[i].copyKey;//去除copyMark,来自递归调用
                    }else{
                        copy_obj[i]=obj[i];
                        delete copy_obj[i].copyKey;//去除copyMark,来自赋值
                        delete obj[i].copyKey;//去除copyMark,来自赋值
                    }
                }else{
                    copy_obj[i]=obj[i];
                }
                index++;
            }
            return copy_obj;
        }
        var newObj=objCopy(obj1);
        console.log(obj1);
        console.log(obj2);
        console.log(newObj);


运行结果正常


至此解决了深拷贝循环引用出现的错误,当然objCopy里mark的实现方式,或许不太好,但是这并不影响实现这个功能的原理,这段代码的功能肯定也是可以再更加好的方式优化,或者还有其他没测试出来的小BUG,在这里就不花时间了,仅仅抛砖引玉一下,因为我想去研究其他的问题了。

另外,你还可以使用

var newObj2=JSON.parse(JSON.stringify(obj1));

这样的方式来实现深拷贝,不过对于循环引用的数据或者一些特殊类型也是不支持的会报错,但是对于后端传递出来的JSON数据,是简单可行的。

评论 ( 0 )

网友留言