浅拷贝与深拷贝

387 阅读6分钟

浅拷贝与深拷贝是对于引用类型来说的,

引用类型的值实际存储在堆Heap内存中,它在栈Stack中只存储了一个固定长度的地址,这个地址指向堆内存中的值。

浅拷贝

浅复制是对对象地址的复制,并没 有开辟新的堆,也就是复制的结果是两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变;

使用concat和slice、concat、Array.from()、Array.of()浅拷贝方法时请注意,改变原数组(或拷贝后的数组)其中的原始类型项并不会引起另外一个数组的改变,只有改变引用类型项才会引起另外一个数组的改变,具体请查看下文【浅拷贝注意事项】

,

深拷贝

深复制则是开辟新的堆,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性, 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

数组

数组浅拷贝

方法 举例 描述
concat var new_arr = arr.concat(); 合并两个或多个数组。不改变现有数组,而是返回一个新数组
slice var new_arr = arr.slice() 返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)slice(begin,end)。原始数组不会被改变。
Array.from() var arr1 = Array.from(arr) 方法从一个类似数组或可迭代对象中创建一个新的,浅拷贝的数组实例
Array.of() var arr1 = Array.of(...arr);//需要结合扩展运算符... 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
copyWithin() var arr1 =arr.copyWithin() 浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。

使用concat和slice、concat、Array.from()、Array.of()浅拷贝方法时请注意,改变原数组(或拷贝后的数组)其中的原始类型项并不会引起另外一个数组的改变,只有改变引用类型项才会引起另外一个数组的改变。具体请查看下文

数组深拷贝

方法 举例 描述
JSON.parse(JSON.stringify(arr) var arr1 = JSON.parse(JSON.stringify(arr)) 适用于所有引用类型
递归 let a = [1,2,3,4];let b = [];a.forEach(item=>{b.push(item)}); 可以使用for、forEach等循环遍历来拷贝

对象

对象浅拷贝

方法 举例 描述
Object.assign() var copy = Object.assign({}, obj); 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象

对象深拷贝

方法 举例 描述
JSON.parse(JSON.stringify(arr) var obj1 = JSON.parse(JSON.stringify(obj)) 适用于所有引用类型
递归 for/等 适用于所有引用类型

浅拷贝注意事项

使用concat和slice、concat、Array.from()、Array.of()浅拷贝方法时请注意,改变原数组(或拷贝后的数组)其中的原始类型项并不会引起另外一个数组的改变,只有改变引用类型项才会引起另外一个数组的改变,例如:

        var arr = [{name:"hello"}, 1, "world"];
        var arr1 = arr.concat();
        arr1[0].name = "辣鸡";
        arr1[1] = 2;
        console.log(arr); //[{name:"辣鸡"}, 1, "world"]
        console.log(arr1); //[{name:"辣鸡"}, 2, "world"]
        

copyWithin()

        var arr = [{name:"hello"}, 1, "world"];
        var arr1 =arr.copyWithin();
        
        arr1[0].name = "辣鸡";
        arr1[1] = 2;
        arr1[2] = "怎么了";
        console.log(arr); //[{name:"辣鸡"}, 2, "怎么了"]
        console.log(arr1); //[{name:"辣鸡"}, 2, "怎么了"]

Array.of()

Array.of() 数组浅拷贝

var arr = [{name:"hello"}, 1, "world"];
var arr1 = Array.of(arr)
arr1[0].name = "辣鸡"

console.log(arr); //[{name:"辣鸡"}, 1, "world"]
console.log(arr1); //[{name:"辣鸡"}, 1, "world"]

arr原数组打印值

arr1拷贝后的值

Array.of() 缺点

拷贝后得到的数组length是1,所以需要结合扩展运算来实现,如下:

这样得到的数组长度才不会改变

JSON.parse

深拷贝

先使用JSON.stringify将一个 JavaScript 值(对象或者数组)转换为一个 JSON 字符串,此时存储发生改变,内存中会加一个堆来存储这个字符串

再使用JSON.parse 解析JSON字符串(将一个 JSON 字符串转换为对象)。栈中会有生成一个栈来存储这个引用对象(数组或基础对象),再在对应堆中存储这个栈地址

let obj = {a:{b:22}};
let copy = JSON.parse(JSON.stringify(obj));

优点 : 代码写起来比较简单。

缺点 :

  • 你先是创建一个临时的,可能很大的字符串,只是为了把它重新放回解析器。
  • 这种方法不能处理循环对象。如下面的循环对象用这种方法的时候会抛出异常

let a = {};
let b = {a};
a.b = b;
let copy = JSON.parse(JSON.stringify(a));

诸如 Map, Set, RegExp, Date, ArrayBuffer 和其他内置类型在进行序列化时会丢失。

let a = {};
let b = new Set();
b.add(11);
a.test = b;
let copy = JSON.parse(JSON.stringify(a));

a 的值打印如下:

copy的值打印如下

对比发现,Set已丢失。

Object.assign()

Object.assign(target, ...sources);

target:目标对象。sources源对象。

具体请查看MDN的Object.assign()描述

+ 如果要用Object.assign进行浅拷贝时

    1. Object.assign(target, ...sources)中的source尽量是拷贝一个纯基本对象,类似于{name:"辣鸡", age:15, 1:5}并且该对象的每一项属性都应该是可枚举的,而不是(原始类型或不可枚举属性)
    1. Object.assign(target, ...sources)里的target最好是{},要不然会连target也跟着变,所以vue中深入响应式原理中提到

使用Object.assign(target, ...sources)进行浅拷贝的正确演示如下:

var obj = { a: 1, b: 2, c: 3 };
var obj1 = Object.assign({},obj);//target是{},sources是可枚举的基本对象

对于source对象,如上面的obj,但是如果obj每一项不全是原始对象,那么改变obj1里原始类型的对象并不会引起obj的改变,但是改变引用类型项里的某一项,则会引起obj的改变,例如

var obj = { a: 1, b: {b1:2}, c: 3 };
var obj1 = Object.assign({},obj);//target是{},sources是可枚举的基本对象
obj1["a"] = "辣鸡";
obj1["b"]["b1"] = "呵呵";
console.log(obj);//
console.log(obj1);//

相关坑

使用vue作为js框架,antdesign-vue作为UI框架时,取消弹框明明没有发射emit事件给父组件进行改变值,为什么还是会改变父组件里的值

原因就是,在父组件传递给子组件值的时候,没有进行深拷贝,造成的后果就是,在子组件里修改了传递过来的值,父组件的值也进行了改变,即使没有发射emit事件。

切记切记呀!!!!

参考