赋值,浅拷贝,深拷贝

98 阅读6分钟

数据的储存方式

基础数据类型

我们可以看到基础数据类型是是存放在栈中的

image.png

引用数据类型

引用类型,则是在栈中存放引用类型在堆中的地址

image.png

赋值,浅拷贝,深拷贝的区别

赋值

基础类型

let num1 = 1;
let num2 = num1;


console.log(num1);//1
console.log(num2);//1

当我们将num1赋值给num2,此时会在栈中开辟一个新空间来储存num2,让我们来看看此时栈中的数据

image.png

我们此时来验证一下,num1和nm2是否是不同空间

num2++;
console.log(num1);//1
console.log(num2);//2

此时num2发生改变,而num1未发生改变,它们互相并不影响,此时在栈中的情况如图

image.png

引用类型

我们前面讲过,引用类型在栈中存放的是堆中的地址,而引用类型的赋值则是将地址赋值给另一个对象

let obj1 = {
  name: "tom",
  age: 18
};

let obj2 = obj1;

console.log(obj1);//{"name":"tom","age":"18"}
console.log(obj2);//{"name":"tom","age":"18"}

我们将obj1赋值给obj2,实际上是将obj1的地址赋值给obj2,如图:

image.png

他们都指向堆中的同一个地址,此时我们来验证一下

obj1.name = "jack";
console.log(obj1);//{"name":"jack","age":"18"}
console.log(obj2);//{"name":"jack","age":"18"}

我们可以看到此时,obj1和obj2的name都被改变了说明他们指向同一个空间

当然,这时候就有聪明的同学问了,如果引用类型里面还有引用类型呢? 问的好,我们直接上图

image.png

接下来我们验证一下

```js
let obj1 = {
  name: "tom",
  age: "18",
  family: {
      mom: "Ada",
      dad: "ben"
  }
};

let obj2 = obj1;

console.log(obj1);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}
console.log(obj2);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}


obj2.family.dad = "Cary";



console.log(obj1);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"Cary"}}
console.log(obj2);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"Cary"}}

我们发现改变obj2的值,依旧改变了obj1,说明它们仍然指向同一块空间。

但是我们要两个引用类型各自互不干扰,各自占用各自的空间,我们该怎么做呢,此时就引出浅拷贝和深拷贝

浅拷贝

浅拷贝将引用类型的基础类型重新开辟空间进行储存,而引用类型则依旧指向原来的空间,如图:

image.png

上代码

//这里shallowClone为浅拷贝的方法,实现方式后面会讲,这里主要注重浅拷贝的效果

let obj1 = {
  name: "tom",
  age: "18",
  family: {
      mom: "Ada",
      dad: "ben"
  }
};

let obj2 = shallowClone(obj1);  //这里的shallowClone为浅拷贝函数

console.log(obj1);  //{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}
console.log(obj2);  //{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}

obj1.name = "jack"; 
obj2.name = "rose";

console.log(obj1);  //{"name":"jack","age":"18","family":{"mom":"Ada","dad":"ben"}}
console.log(obj2);  //{"name":"rose","age":"18","family":{"mom":"Ada","dad":"ben"}}

obj1.family.dad = "Cary";

console.log(obj1);  //{"name":"jack","age":"18","family":{"mom":"Ada","dad":"Cary"}}
console.log(obj2);  //{"name":"rose","age":"18","family":{"mom":"Ada","dad":"Cary"}}

可以看到,我们浅拷贝过后:

  1. 分别修改obj1, obj2的name属性,互不干扰。
  2. 通过obj1修改family的dad属性,发现此时obj2的family的dad也跟着改变了

深拷贝

深拷贝则是将引用类型里面的引用类型和基础类型全部开辟新空间进行储存,如图:

image.png

上代码:

//此时的deepClone为深拷贝方法,实现方式后面会讲,这里主要注重深拷贝的效果


let obj1 = {
  name: "tom",
  age: "18",
  family: {
      mom: "Ada",
      dad: "ben"
  }
};

let obj2 = deepClone(obj1);  //这里的shallowClone为浅拷贝函数

console.log(obj1);  //{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}
console.log(obj2);  //{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}

obj1.name = "jack"; 
obj2.name = "rose";

console.log(obj1);  //{"name":"jack","age":"18","family":{"mom":"Ada","dad":"ben"}}
console.log(obj2);  //{"name":"rose","age":"18","family":{"mom":"Ada","dad":"ben"}}

obj1.family.dad = "Cary";

console.log(obj1);  //{"name":"jack","age":"18","family":{"mom":"Ada","dad":"Cary"}}
console.log(obj2);  //{"name":"rose","age":"18","family":{"mom":"Ada","dad":"ben"}}

obj2.family.dad = "Bob";

console.log(obj1);  //{"name":"jack","age":"18","family":{"mom":"Ada","dad":"Cary"}}
console.log(obj2);  //{"name":"rose","age":"18","family":{"mom":"Ada","dad":"Bob"}}

可以看到深拷贝过后:

  1. obj1,obj2的基础类型修改,互不影响
  2. obj1,obj2的引用类型(即family属性)修改,也互不影响

接下来我们将来实现深浅拷贝

浅拷贝实现

数组

  1. Array.prototype.concat()
let arr1 = ["tom",18,["rose",18]]

let arr2 = arr1.concat()


console.log(arr1)//["tom",18,["rose",18]]
console.log(arr2)//["tom",18,["rose",18]]

arr2[0] = "jack"
arr2[2][0] = "Marry"

console.log(arr1)//["tom",18,["Marry",18]]
console.log(arr2)//["jack",18,["Marry",18]]
  1. Array.prototype.slice()
let arr1 = ["tom",18,["rose",18]]

let arr2 = arr1.slice()


console.log(arr1)//["tom",18,["rose",18]]
console.log(arr2)//["tom",18,["rose",18]]

arr2[0] = "jack"
arr2[2][0] = "Marry"

console.log(arr1)//["tom",18,["Marry",18]]
console.log(arr2)//["jack",18,["Marry",18]]

对象

1. Oject.assign()

let obj1 = {
  name: "tom",
  age: "18",
  family: {
      mom: "Ada",
      dad: "ben"
  }
};

let obj2 = Object.assign({},obj1);

console.log(obj1);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}
console.log(obj2);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}


obj2.name = "jack";
obj2.family.dad =  "Cary"


console.log(obj1);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"Cary"}}
console.log(obj2);//{"name":"jack","age":"18","family":{"mom":"Ada","dad":"Cary"}}

此时我们发现修改obj2的name属性,并没有影响obj1的name属性,而修改obj2的family属性的dad属性,影响到了obj1的属性,浅拷贝成功

通用

1. 扩展运算符

对象
let obj1 = {
  name: "tom",
  age: "18",
  family: {
      mom: "Ada",
      dad: "ben"
  }
};

let obj2 = {...obj1};

console.log(obj1);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}
console.log(obj2);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}


obj2.name = "jack";
obj2.family.dad =  "Cary"


console.log(obj1);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"Cary"}}
console.log(obj2);//{"name":"jack","age":"18","family":{"mom":"Ada","dad":"Cary"}}
数组
let arr1 = ["tom",18,["rose",18]]

let arr2 = [...arr1]
console.log(arr1)//["tom",18,["rose",18]]
console.log(arr2)//["tom",18,["rose",18]]


arr2[0] = "jack"
arr2[2][0] = "Marry"
console.log(arr1)//["tom",18,["Marry",18]]
console.log(arr2)//["jack",18,["Marry",18]]

2. 手写循环

function shallowClone(source) {
    let target = Array.isArray(source) ? [] : {};;
    for(let i in source) {
            target[i] = source[i];
    }
    return target;
}

测试:

//对象
let obj1 = {
  name: "tom",
  age: "18",
  family: {
      mom: "Ada",
      dad: "ben"
  }
};

let obj2 = shallowClone(obj1);
console.log(obj1);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}
console.log(obj2);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}


obj2.name = "jack";
obj2.family.dad =  "Cary"
console.log(obj1);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"Cary"}}
console.log(obj2);//{"name":"jack","age":"18","family":{"mom":"Ada","dad":"Cary"}}

//数组
let arr1 = ["tom",18,["rose",18]]
let arr2 = shallowClone(arr1)
console.log(arr1)//["tom",18,["rose",18]]
console.log(arr2)//["tom",18,["rose",18]]


arr2[0] = "jack"
arr2[2][0] = "Marry"
console.log(arr1)//["tom",18,["Marry",18]]
console.log(arr2)//["jack",18,["Marry",18]]

深拷贝实现

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

//对象
let obj1 = {
  name: "tom",
  age: "18",
  family: {
      mom: "Ada",
      dad: "ben"
  }
};

let obj2 = JSON.parse(JSON.stringify(obj1));

console.log(obj1);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}
console.log(obj2);//{"name":"tom","age":"18","family":{"mom":"Ada","dad":"ben"}}


obj2.name = "jack";
obj2.family.dad =  "Cary"

obj1.age = 50
obj1.family.dad =  "Bill"

console.log(obj1);//{"name":"tom","age":50,"family":{"mom":"Ada","dad":"Bill"}}
console.log(obj2);//{"name":"jack","age":"18","family":{"mom":"Ada","dad":"Cary"}}
//数组
let arr1 = ["tom",18,["rose",18]];

let arr2 = JSON.parse(JSON.stringify(arr1));

console.log(arr1);//["tom",18,["rose",18]]
console.log(arr2);//["tom",18,["rose",18]]

arr2[0] = "jack";
arr2[2][0] = "Marry";

arr1[1] = 50; 
arr1[2][2]=60;

console.log(arr1);//["tom",50,["Marry",60]]
console.log(arr2);//["jack",18,["Marry",18]]

我们此时可以看到深拷贝前后的对象,互不影响,但是注意,这个方法不能拷贝函数和正则

2. 手写递归

只考虑简单的object和数组类型的简单版

function clone(target, map = new WeakMap()) {
    if (typeof target === 'object' && target !=== null) {
        let cloneTarget = Array.isArray(target) ? [] : {};
        //解决循环引用
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget); 
        //递归进行拷贝
        for (const key in target) {
            cloneTarget[key] = clone(target[key], map);
        }
        return cloneTarget;
    } else {
        return target;
    }
};

考虑其他类型

这里强烈建议观看这篇文章:

如何写出一个惊艳面试官的深拷贝?

结语

如有不对,请各位大佬指正,非常感谢

感谢:

如何写出一个惊艳面试官的深拷贝?

浅拷贝与深拷贝