深浅拷贝

# 1.深拷贝VS浅拷贝

深拷贝和浅拷贝都是针对的引用类型,JS中的变量类型分为值类型(基本类型)和引用类型;对值类型进行复制操作会对值进行一份拷贝, 而对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据。

// 基本类型
var a = 1;
var b = a;
a = 2;
console.log(a, b); // 2, 1 ,a b指向不同的数据

// 引用类型指向同一份数据
var a = {c: 1};
var b = a;
a.c = 2;
console.log(a.c, b.c); // 2, 2 全是2,a b指向同一份数据

对于引用来说,a和b 指向同一个堆内存中的对象,因为本质上只是栈内存中地址得复制。。当任意修改其中一个的时候,另一个也会被改变。这样在某些时刻可能会给我们带来一些困扰, 更多的时候,我们不希望联动效果的出现。

根据拷贝的层级不同可以分为浅拷贝和深拷贝:

  • 浅拷贝就是只进行一层拷贝

  • 深拷贝就是无限层级拷贝(拷贝到底)

# 2.浅拷贝实现

方式一:

/**
hasOwnProperty 对象是否包含自身属性,非继承属性
**/
function shallowclone(src) {
    let target = {};
    for(let i in src) {
        if(src.hasOwnProperty(i)){
            target[i] = src[i];
        }
    }
    return target;
}

方式二:

Object.assign() 把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。 Object.assign() 进行的是浅拷贝

  • 只有一层的情况下是深拷贝
let obj = {name:'cht' , age:18 , hobby:['game','play']}

let copyObj = Object.assign({} , obj);

console.log(copyObj);  // {name:'cht' , age:18 , hobby:['game','play']}

copyObj.name = 'hw';
console.log(obj.name); // cht

我们可以发现: 当拷贝后的 copyObj修改name值的时候,obj并没有修改。

  • 多层的时候是浅拷贝
let obj = {
    name:'cht',
    friends:{
        name:'hw',
        age:15
    }
}
let copyObj = Object.assign({} , obj);
console.log(copyObj);   //  { name: 'cht', friends: { name: 'hw', age: 15 } }
copyObj.friends.name = 'hw1';
console.log(obj);   // { name: 'cht', friends: { name: 'hw1', age: 15 } }

我们可以发现: 当拷贝后的 copyObj修改friends中name值的时候,obj发生改变修改。

方式三: Array.prototype.concat()

let arr = [1, 2, {name:'cht' , age:12}];
let copyArr = arr.concat();
console.log(copyArr);   //  [ 1, 2, { name: 'cht', age: 12 } ]
copyArr[2].name = 'hw';
console.log(arr);  // [ 1, 2, { name: 'hw', age: 12 } ]

方式三: Array.prototype.slice()

let arr = [1, 2, {name:'cht' , age:12}];
let copyArr = arr.slice(0);
console.log(copyArr);  // [ 1, 2, { name: 'cht', age: 12 } ]
copyArr[2].name = 'hw';
console.log(arr);  // [ 1, 2, { name: 'hw', age: 12 } ]

关于slice 和 concat 个人理解:

  • 当数组中是字符串,数字,布尔值的时候。新拷贝数组修改并不会造成原数组的同步修改

  • 当数组中是引用类型的时候,新拷贝数组修改引用类型中的数据会造成同步修改,也就是上面的两个例子。

# 2.深拷贝的实现

方式一:

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

let obj = {
    name:'cht',
    friends:{
    name:'hw',
    age:15
    }
}
let copyObj = JSON.parse(JSON.stringify(obj));
console.log(copyObj);  //   { name: 'cht', friends: { name: 'hw', age: 15 } }
copyObj.friends.name = 'hw1';
console.log(obj);   //  { name: 'cht', friends: { name: 'hw', age: 15 } }

原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

缺点:

这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。因为 JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数。

方式二:

简单模拟:浅拷贝 + 递归

// 这是比较简单的实现,也就是 浅拷贝 + 递归。 没有考虑数组等 , 判断忽略了null (null 也是 object),性能页比较差,后面会总结全面的。
function depClone(source) {
    let target = {};
    for(let i in source) {
        if(source.hasOwnProperty(i)) {
            if(Object.prototype.toString.call(source[i]) === '[object Object]') {
                target[i] = depClone(source[i]);
            }else {
                target[i] = source[i];
            }
        }
    }
    return target;
}
  let p = {
    name:'cht',
    likes:['eat' , 'sing' , 'dance'],
    friend:{
      name:'hw',
      sex: 2
    }
  }
  let p1 = {};
  depCopy(p1 , p);
  /**
  target: 目标对象,
  source:被拷贝对象
  **/
  function depCopy(target , source) {
    for(let key in source) {
      let sourceVal = source[key];
      if(sourceVal instanceof Object) {
        let subTarget = new sourceVal.constructor;  // 如果 被拷贝对象是一个引用类型,就创建引用类型的实例
        target[key] = subTarget;  // 将引用类型插入到目标对象中
        depCopy(subTarget , sourceVal);
      }else {
        target[key] = sourceVal;
      }
    }
  }
  p1.likes[0] = 'hhhh';
  console.log(p1);
  console.log(p);
Last Updated: 1/17/2021, 11:11:54 PM