深浅拷贝

364 阅读3分钟

拷贝有个大前提,是针对对象的操作,当想复制一个对象的时候,才存在浅拷贝和深拷贝之分。浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。

浅拷贝实现方式 (浅拷贝的意思就是只复制引用,而未复制真正的值)

        1.Object.assign() 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。  可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

        2. ...展开运算符

let obj1 = {name:"Kobe",address:{x:100,y:100}}
let obj2 = {...obj1}
obj1.address.x=200;
obj1.name="wade";
console.log(obj2)   //{name:"Kobe",address:{x:200,y:100}}
// ... 实现的是对象第一层的深拷贝,后面的只是拷贝的引用值

        3. concat()  该方法可以连接两个或者更多的数组, 但是它不会修改已存在的数组,而是返回一个新数组

let arr = [1,3,{
    username:"Kobe"
}];
let arr2 = arr.concat();
arr2[2].username = 'wade';
console.log(arr);
// [1,3,{ username:"wade"};

      4. slice() 该方法不传值将会截取整个字段   slice只是对数组的第一层进行深拷贝

let arr = [1,3,{
    username:'kobe'
}];
let arr2 = arr.slice();
arr2[2].username = 'wade';
console.log(arr) // [1,3,{username:'wade'}]

数组有两个方法 concatslice 是可以实现对原数组的拷贝的,这两个方法都不会修改原数组,而是返回一个修改后的新数组。

深拷贝实现方式(深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了)

      1. JSON.parse(JSON.stringify()) 

JSON.stringify 是将一个 JavaScript 值转成一个 JSON 字符串。

JSON.parse 是将一个 JSON 字符串转成一个 JavaScript 值或对象。

可以处理数组和对象的深拷贝,但是不能处理函数和正则,因为这两者基于这两个函数处理后得到的结果不再是正则/函数

 缺点: 

         1.会忽略undefined

         2.会忽略symbol

         3.不能序列化函数

         4.不能解决循环引用的对象

     2.如果所拷贝的对象含有内置对象,但是不包含函数,可以使用`messagechannel`,可以拷贝undefined和循环引用的对象

function structuralClone(obj) {
        return new Promise(resolve => {
          const { port1, port2 } = new MessageChannel()
          port2.onmessage = ev => resolve(ev.data)
          port1.postMessage(obj)
        })
      }
      
      var obj = {
        a: 1,
        b: {
          c: 2
        }
      }
      
      obj.b.d = obj.b
      
      // 注意该方法是异步的
      // 可以处理 undefined 和循环引用对象
      const test = async () => {
        const clone = await structuralClone(obj)
        console.log(clone)
      }
      test()

     3.递归 (递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值 的操作)

function deepClone(source){
  const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
  for(let keys in source){ // 遍历目标
    if(source.hasOwnProperty(keys)){
      if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
        targetObj[keys] = source[keys].constructor === Array ? [] : {};
        targetObj[keys] = deepClone(source[keys]);
      }else{ // 如果不是,就直接赋值
        targetObj[keys] = source[keys];
      }
    } 
  }
  return targetObj;
}

//演示
const originObj = {  name:'axuebin',
  sayHello:function(){
    console.log('Hello World');
  }
}
console.log(originObj); // {name: "axuebin", sayHello: ƒ}
const cloneObj = deepClone(originObj);
console.log(cloneObj); // {name: "axuebin", sayHello: ƒ}

总结: 

       1. 赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值;

       2. Javascript中数组和对象自带的拷贝方法都是"首层浅拷贝";

       3. JSON.stringify() 实现的是深拷贝,但是对目标对象有要求

       4. 若想真正意义上的深拷贝还是递归。