js的深拷贝、浅拷贝


前言

在了解深浅拷贝之前,我给大家先捋顺一下思路
1、要首先知道什么是基本数据类型,什么是引用数据类型
2、要记住,深浅拷贝是主要针对引用数据类型的
3、请铭记!对于基本数据类型而言其实就是值的拷贝,会开辟相应空间进行存储,互不影响


一、基本类型和引用类型

1.1基本类型

string、number、boolean、null、undefined、symbol

1.2引用类型

Function、Array、Object

二、深浅拷贝怎么理解?

注意:

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。

对于基本数据类型而言其实就是值的拷贝,会开辟相应空间进行存储,互不影响

2.1 基本类型拷贝图解

基本数据类型的值都存在栈内存中,拷贝其实就是值的拷贝,会开辟相应的栈空间存储数据,改变新值或者旧值互不影响

如let a = 1;

在这里插入图片描述

当let b = a时,b复制了a,栈内存会新开辟一个内存

在这里插入图片描述

所以两者互不影响,修改谁都不会影响另一个!

举个栗子:

    // 假设我这里设置a=1,然后把a赋值给b,在改变a的值为3
    // 那么打印a是3,是修改后的值,而打印b还是1,因为a的改变不影响b
    var a = 1;
    var b = a;
    a = 3;
    console.log(a); //3
    console.log(b); //1
    // 同样的,假设我改变b的值为3,它同样也不影响a的值,a还是1
    var a = 1;
    var b = a;
    b = 3;
    console.log(a); //1
    console.log(b); //3

2.2 浅拷贝

浅拷贝其实是指拷贝了“指向堆内存的地址”,拷贝后的对象和原对象指向同一个地址,因此修改新对象或者原对象,都会互相干扰

例如:

将一个Object对象所有的属性和属性值复制给另一个Object对象。这样导致的后果是,新对象和老对象相同的Object类型的属性值在内存中的指向是一样的,也就是新对象和老对象的Object类型的同一属性值指向的是同一块内存。因此修改新对象或者原对象,都会互相干扰。

2.1.1 浅拷贝图解( 引用数据类型)

地址在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址,指向堆内存中存储的值

例如:我有一个引用类型的a,它的地址在栈内存中,但是数据则是存储在堆内存中,栈内存会提供一个引用地址,指向堆内存中存储的值

在这里插入图片描述

b复制了a后, b其实是生成了一个新地址,地址指向和a地址指向为同一个堆数据,而不是把a的值复制了下来,其实他们用的还是同一组数据,并没有拷贝出新数据

在这里插入图片描述

所以改了b,a也会受到影响。

举个栗子:

    // 假设我有一个引用类型arr的数据,arr1拷贝了arr,
    // 然后我把arr的name改成了李四,那么打印arr和arr1都是修改后的值
    // 这是因为,arr1在拷贝时,并没有开辟空间去存储arr的值,只是单纯引用了arr值的存储地址而已,地址都指向了同一组数据
    var arr = [{ name: "张三" }, { age: "10" }];
    var arr1 = arr;
    arr[0].name = "李四";
    console.log(arr); //[{ name: "李四" }, {age: "10" }];
    console.log(arr1); //[{ name: "李四" }, {age: "10" }];
    // 同样的,我如果改变arr1的name,打印结果也是修改后的
    var arr = [{ name: "张三" }, { age: "10" }];
    var arr1 = arr;
    arr1[0].name = "李四";
    console.log(arr); //[{ name: "李四" }, {age: "10" }];
    console.log(arr1); //[{ name: "李四" }, {age: "10" }];

2.3 深拷贝

深拷贝是基于引用型出来的一种技术手段,不仅会在栈中开辟另一块空间,还会在堆内存中开辟另一块空间存储引用类型的真实数据。拷贝后的对象和原对象不指向同一个地址,因此修改新对象或者原对象,都不会互相干扰

例如:

将一个Object对象的内容完全拷贝一份给新对象。修改原对象的属性或者属性值,都不会影响新对象。原对象和新对象是完全独立的,互不影响。

2.3.1 深拷贝图解( 引用数据类型)

例如,我有一个obj1的引用类型的对象,它的地址在栈空间中,而数据存储在堆空间里面,栈内存会提供一个引用地址,指向堆内存中存储的值

在这里插入图片描述

然后我设置了一个新的对象obj2,用来深拷贝obj1对象

在这里插入图片描述

然后,obj2会在堆内存中也开辟一个新的堆内存专门为Obj2存放值的地方,并拷贝一份obj1的值放进去,这时候,obj1和obj2的地址指向不是同一个,就像基本类型那样。

在这里插入图片描述

而当我们Obj1[0]=9时进行数组修改时,由于Obj1与Obj2指向的是不同的地址,所以Obj2不受影响,这就是深拷贝。

三、浅拷贝的实现方式

重要的话说三遍!!!
如果数组元素是基本类型,就是值的拷贝,会开辟存储空间,互不影响,
如果数组元素是基本类型,就是值的拷贝,会开辟存储空间,互不影响,
如果数组元素是基本类型,就是值的拷贝,会开辟存储空间,互不影响,

   // 如果数组元素是基本类型,就会拷贝一份,互不影响,
    var arr = [1, 2, 3];
    var brr1 = arr.concat();
    var brr2 = arr.slice();
    var brr3 = [...arr];
    var brr4 = Object.assign([], arr);
    var brr5 = Array.from(arr);
    arr.push(4);
    console.log(arr); // [1, 2, 3, 4]
    console.log(brr1); //[1, 2, 3]
    console.log(brr2); //[1, 2, 3]
    console.log(brr3); //[1, 2, 3]
    console.log(brr4); //[1, 2, 3]
    console.log(brr5); //[1, 2, 3]

如果是引用数据类型,就会只拷贝对象和数组的引用,无论对新旧数组的哪一个进行了修改,两者都会发生变化。

    var arr = [{ name: "张三" }, { age: "10" }]; //原数组
    var newArr1 = arr.slice();
    var newArr2 = arr.concat();
    var newArr3 = Object.assign([], arr);
    var newArr4 = Array.from(arr);
    arr[0].name = "李四";
    arr[1].age = "20";
    console.log(arr); //  [{ name: "李四" }, { age: "20" }]
    console.log(newArr1); //[{ name: "李四" }, { age: "20" }]
    console.log(newArr2); //[{ name: "李四" }, { age: "20" }]
    console.log(newArr3, "newArr3"); //[{ n张三ame: "李四" }, { age: "20" }]//浅拷贝
    console.log(newArr4, "newArr4"); //[{ name: "李四" }, { age: "20" }]//浅拷贝

3.1 浅拷贝实现的方法总结

1、concat( )
Array的concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

2、slice( )
Array的slice方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

3、拓展运算符 (…)
扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中

4、Object.assign(‘目标对象’,‘源对象’)
方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。

5、Array.from( )

四、深拷贝的实现方式

4.1 JSON.parse(JSON.stringify(obj))

通过JSON.stringify把对象转成基本类型,再用JSON.parse把字符串(基本类型)转成一个全的新的对象;JSON.parse这个方法会在堆中开辟新的空间。所以这里是利用了JSON.parse的这个特性做到的深拷贝
实际项目开发过程中,这种方法其实使用的最多

缺点:不会拷贝对象上的 value 值为 undefined 和 函数的键值对

   //数组形式的数据展示
    let list = [{ name: "张三" }, { age: "20" }];
    let copyList = JSON.parse(JSON.stringify([...list]));
    list[0].name = "李四";
    list[1].age = "30";
    console.log(list); // [{ name: "李四" }, { age: "30" }]
    console.log(copyList); //  [{ name: "张三" }, { age: "20" }]
    
   //对象形式的数据展示
    let list = { name: "张三",  age: "20" };
    let copyList = JSON.parse(JSON.stringify({...list}));
    list.name = "李四";
    list.age = "30";
    console.log(list); // [{ name: "李四" }, { age: "30" }]
    console.log(copyList); //  [{ name: "张三" }, { age: "20" }]

4.2 _.cloneDeep

函数库lodash,也有提供_.cloneDeep用来做深拷贝,安装后使用
官网:https://www.lodashjs.com/
缺点:需要下载依赖包,对性能有影响

    //数组形式的数据展示
    let lodash = require('lodash');
    let list = [{ name: "张三" }, { age: "20" }];
    let copyList = lodash.cloneDeep(list);
    list[0].name = "李四";
    list[1].age = "30";
    console.log(list); //  [{ name: "李四" }, { age: "30" }]
    console.log(copyList); // [{ name: "张三" }, { age: "20" }]
    
    //对象形式数据展示
    let lodash = require('lodash');
    let list = { name: "张三" , age: "20" };
    let copyList = lodash.cloneDeep(list);
    list.name = "李四";
    list.age = "30";
    console.log(list); //  [{ name: "李四" }, { age: "30" }]
    console.log(copyList); // [{ name: "张三" }, { age: "20" }]

4.3 递归实现

缺点:写起来比较繁琐,容易出bug,不是很建议使用

function deepClone(obj) {
    let objClone = Array.isArray(obj) ? [] : {};
    if (obj && typeof obj === "object") {
        for (key in obj) {
            if (obj.hasOwnProperty(key)) {
                //判断ojb子元素是否为对象,如果是,递归复制
                if (obj[key] && typeof obj[key] === "object") {
                    objClone[key] = deepClone(obj[key]);
                } else {
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}
let a = [1, 2, 3, 4],
    b = deepClone(a);
a[0] = 2;
console.log(a, b)	// [2,2,3,4]  [1,2,3,4]

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值