js深拷贝,解决循环引用

概念

前提为拷贝类型为引用类型的情况下:

  1. 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址

  2. 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

浅拷贝的方法:

 1. Object.assign() 
 2. Array.prototype.slice/ Array.prototype.concat()
 3. ... 扩展运算符
 4. Object.create() // 这个创建的原型对象也是浅拷贝
 

深拷贝方法:

 1. _.cloneDeep()

 2. jQuery.extend()

 3. JSON.stringify() 
   缺点:使用JSON.stringify()以及JSON.parse()它是不可以拷贝 undefined ,symbol function, RegExp 等等类型的
 4. 手写循环递归 (循环引用要注意,使用weakMap解决)

浅拷贝

浅拷贝的意思就是只复制引用,而未复制真正的值。

const originArray = [1,2,3,4,5];
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

const cloneArray = originArray;
const cloneObj = originObj;

console.log(cloneArray); // [1,2,3,4,5]
console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}}

cloneArray.push(6);
cloneObj.a = {aa:'aa'};

console.log(cloneArray); // [1,2,3,4,5,6]
console.log(originArray); // [1,2,3,4,5,6]

console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}

上面的代码是最简单的利用 = 赋值操作符实现了一个浅拷贝,可以很清楚的看到,随着 cloneArray 和 cloneObj 改变,originArray 和 originObj 也随着发生了变化。

注意:容易忽略的Object.create()

let parent4 = {
    name: "parent4",
    friends: ["p1", "p2", "p3"],
    getName: function() {
      return this.name;
    }
  };

  let person4 = Object.create(parent4);
  person4.name = "tom";
  person4.friends.push("jerry");

  let person5 = Object.create(parent4);
  person5.friends.push("lucy");
  console.log(person4.name); // tom
  console.log(person4.name === person4.getName()); // true
  console.log(person5.name); // parent4
  console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"]
  console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]

因为Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,因此修改一个会互相影响。

深拷贝

代码如下:
循环递归实现。

     // 使用weakmap解决循环引用
     // 优点:
     // 1 .WeakMap来记录对象是否被克隆,主要考虑一下三点。
    //  2 .WeakMap对象是key=>value形式,不会重复记录
    //  3 .WeakMap是弱引用,如果不在使用,空间会直接释放

     function deepCopy (obj, hash= new WeakMap()) {
       // 不是对象(普通值类型/function),null,undefined,正则,Date都会直接返回
       if(obj == null || typeof obj != 'object') {
        return obj
       }
       if(obj instanceof RegExp ) {
          return new RegExp(obj)
       }
       if( obj instanceof Date) {
        return new Date(obj)

       }

       // 判断是否循环引用的(判断属性是不是存在了)
       if(hash.get(obj)) return hash.get(obj)

       let cloneObj = new obj.constructor()

      // 存obj
        hash.set(obj, cloneObj)

       for(let key in obj) {
        // in 循环会遍历原型链的,所以需要判断是否是当前对象的属性
        if(obj.hasOwnProperty(key)) {
          cloneObj[key] = deepCopy(obj[key], hash)
        }
       }

       return cloneObj
       
     }

其他方法拷贝(都有一些缺点):

Object.assign()
缺点: 当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。

 // 接着来看第二种方式 Object.assign(target, source)
    var obj1 = {
       name: 'zs',
       age: 18,
       sex: '男',
       dog: {
         name: '金毛',
         age: 2,
         yellow: '黄色'
       },
       friends: ['kele','jiawen'],
       say: function () {
         console.log(1)
       },
       obj: {
         a: null,
         b: undefined,
         arr:[1,2,3]
       },
       c: /a/,
       d: null,
       e:{}
     }

     var obj2 = Object.assign({}, obj1);
    // 缺点: 当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。

JSON方法
缺点:使用JSON.stringify()以及JSON.parse()它是不可以拷贝 undefined , function,symbol, RegExp 等等类型的

var obj1 = {
       name: 'zs',
       age: 18,
       sex: '男',
       dog: {
         name: '金毛',
         age: 2,
         yellow: '黄色'
       },
       friends: ['kele','jiawen'],
       say: function () {
         console.log(1)
       },
       obj: {
         a: null,
         b: undefined,
         arr:[1,2,3]
       },
       c: /a/,
       d: null,
       e:{}
     }
    // 利用JSON方法
    //  缺点:使用JSON.stringify()以及JSON.parse()它是不可以拷贝 undefined , function, RegExp 等等类型的
    function copy (obj) {
      var start = JSON.stringify(obj); // 返回新字符串,不影响原来对象
      var result = JSON.parse(start);
      return result;

    }
    var a = copy(obj1);
    console.log(a);
    a.friends.push('xixi')
    console.log(obj1)
//     lodash函数库实现深拷贝
// lodash很热门的函数库,提供了 lodash.cloneDeep()实现深拷贝

JQuery实现

var obj1 = {
       name: 'zs',
       age: 18,
       sex: '男',
       dog: {
         name: '金毛',
         age: 2,
         yellow: '黄色'
       },
       friends: ['kele','jiawen'],
       say: function () {
         console.log(1)
       },
       obj: {
         a: null,
         b: undefined,
         arr:[1,2,3]
       },
       c: /a/,
       d: null,
       e: undefined
     }
     // true进行深拷贝,{}要拷贝到这来
var newArray = $.extend(true,{},obj1);
// undefined没有拷贝
console.log(newArray)
newArray.dog.name = 'lala'
console.log(obj1)

lodash函数库实现深拷贝
lodash很热门的函数库,提供了 lodash.cloneDeep()实现深拷贝

总结2:如果想更深入去学习。
推荐看这个:
https://github.com/yygmind/blog/issues/29

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaScript 中,循环引用是指当一个对象中存在对自身的引用时。处理循环引用时,我们需要使用深拷贝来创建一个对象的副本,以避免出现无限递归的问题。以下是一种可以解决循环引用深拷贝方法: ```javascript function deepCopy(obj, cache = new WeakMap()) { // 如果不是对象类型,则直接返回 if (!(obj instanceof Object)) { return obj; } // 如果已经拷贝过该对象,则直接返回该拷贝对象 if (cache.has(obj)) { return cache.get(obj); } // 根据对象的类型创建一个新的空对象 let clone; if (obj instanceof Array) { clone = []; } else if (obj instanceof Date) { clone = new Date(obj.getTime()); } else if (obj instanceof RegExp) { clone = new RegExp(obj); } else { clone = {}; } // 将新创建的对象添加到缓存中 cache.set(obj, clone); // 遍历原对象的属性,并递归进行深拷贝 for (let key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepCopy(obj[key], cache); } } return clone; } ``` 使用该方法,可以深度拷贝包含循环引用的对象,避免出现无限递归的问题。例如: ```javascript const obj = { foo: 'bar' }; obj.self = obj; const clone = deepCopy(obj); console.log(clone); // { foo: 'bar', self: [Circular] } console.log(clone === obj); // false ``` 上述代码中,我们创建了一个对象 `obj`,它包含了一个对自身的引用。使用 `deepCopy` 方法对 `obj` 进行深拷贝后,得到了一个新的对象 `clone`,该对象也包含了对自身的引用。但是由于进行了深拷贝,所以不会出现无限递归的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值