js的浅拷贝和深拷贝

一、如何区分浅拷贝和深拷贝

简单来说,如果b复制了a,当修改a时,看b是否会发生变化,如果b也变了,说明这是浅拷贝,如果b没变,说明这是深拷贝。

  1. 先看👀个浅拷贝的例子:

明明b复制了a,修改数组a,为什么数组b也会跟着变呢?这就得引入基本数据类型和引用数据类型的概念了。

二、基本数据类型与引用数据类型

基本数据类型有:number、string、boolean、null、undefined、symbol以及未来ES10新增的BigInt(任意精度整数)七类

引用数据类型有:对象、数组、函数等。

这两类的数据存储分别是这样的:

  1. 基本数据类型--名和值存储在栈内存中

举例:

let a=1;

当设置b=a时,栈内存会开辟一个新内存:

当设置a=2时,对b并没有影响。

虽然这里b不受a的影响,这也算不上是深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。

  1. 引用数据类型--名存在栈内存中,值存在堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值。

举例:

let a = ["a", "b", "c", "d"]

当设置b=a进行拷贝时,其实复制的是a的引用地址,而不是堆内存里面的值。

当设置a[0]='test'时,由于a与b指向的是同一个地址,所以b也受了影响,这就是所谓的浅拷贝。

如果在堆内存中也开辟一个新的内存给b存放值,就像基本数据类型那样,岂不就是能达到深拷贝的效果了。例如下面这样:

三、如何实现深拷贝

  1. 封装一个简单的深拷贝函数,用递归去复制所有层级属性

逐层递归的代码示例:

const copyObj = (obj = {}) => {
    //变量先置空
    let newObj = null;
    //判断是否需要继续进行递归
    if (typeof (obj) == 'object' && obj !== null) {
        newObj = obj instanceof Array ? [] : {};
        //进行下一层递归克隆             
        for (let i in obj) {
            newObj[i] = copyObj(obj[i]);
        }
    } else {
        //如果不是对象直接赋值
        newObj = obj;
    };
    return newObj;
};

let obj = {
    numberParam: 1,
    functionParam: () => {
        console.log('测试属性值是函数');
    }
};
const newObj = copyObj(obj);
obj.numberParam = 10;  
console.log("obj=>",obj); 
console.log("newObj=>",newObj); 
  1. 借用JSON对象的JSON.stringify()和JSON.parse()

代码示例:

function deepClone(obj) {
    return JSON.parse(JSON.stringify(obj));
}
let a = {
    b: {
        c: 1
    }
};
let b = deepClone(a);
a.b.c = "test";
console.log("a=>", a);
console.log("b=>", b);

⚠️当对象内容项为number,string,boolean时,没有什么问题。但是,当对象内容项为undefined,null,Date,RegExp,function,error时,使用JSON.stringify()进行拷贝就会出问题。

3、使用第三方库lodash中的cloneDeep()方法

如果项目中只需要一个深拷贝的功能,这种情况下为了一个功能引入整个第三方库就显得很不值得了。不如写一个递归函数对于项目来说性能更好。

lodash.cloneDeep()代码示例:

import lodash from 'lodash';
let obj = {
    a: {
        c: 1,
        d: [1, 3, 5]
    },
    b: 2
};
const newObj = lodash.cloneDeep(obj);
obj.b = 20; 
console.log(newObj.b);//输出2

4、jquery的extend()方法(推荐在JQ中使用)

这个方法仅适用于JQuery的项目。JQuery自身携带的extend()方法可以进行深拷贝,不用自己写递归也不用引入第三方库。

代码示例:

let obj = {
    a: {
        c: 1,
        d: [1, 3, 5],
    },
    b: 2
}
let newObj = $.extend(true, {}, obj);
obj.b = 20; 
console.log(newObj.b); //输出2

如果$.extend()没有传true不能深拷贝到第二层:

 let obj1 = {
    a: {
        d: [1, 2, 3]
    }
}
// $.extend()没有传true
let newObj1 = $.extend({}, obj1);
obj1.a.d[2] = 20;
console.log("没有true-->", newObj1);

let obj2 = {
    a: {
        d: [1, 2, 3],
    }
}
// $.extend()传true
let newObj2 = $.extend(true, {}, obj2);
obj2.a.d[2] = 20;
console.log("有true-->", newObj2);

5、JS原生的深拷贝:structuredClone()

    const obj = {
        id: 'abcd1234',
        values: ['a', 'b']
    };
    const clone2 = structuredClone(obj);

    clone2.values.push('x');
    console.log("obj->", obj);
    console.log("clone2->", clone2);

四、总结:

1、进行深拷贝的方法

  • 递归函数 (推荐使用,项目中使用的更多,更小,更安全)

  • JSON.stringify() 和JSON.parse() ; (不推荐使用,如果遇到Function,Date等类型的变量容易出现一些意料之外的问题)

  • 第三方库lodash的cloneDeep()方法 (就情况而定,如果项目中原先就有lodash这个第三方库,可以使用,否则还是推荐使用递归函数,不然成本太高。)

  • JQuery的extend()函数 (推荐在JQuery项目中使用,其他项目依然推荐是用递归函数)

  • structuredClone() 方法是浏览器原生支持的,可拷贝无限嵌套的对象和数组,但对于 Function 类型会报错,但最主要的缺点是兼容性问题。

2、注意⚠️

(1)数组的concat()方法和slice()方法都不是真正的深拷贝

数组的concat()方法和slice()方法,拷贝的不彻底,第一层的属性确实是深拷贝,拥有了独立的内存,但更深的属性仍然公用了地址,说明数组的concat()方法和slice()方法都不是真正的深拷贝。

(2)ES6的扩展运算符执行的是浅拷贝

const obj = {
     id: 'abcd1234',
     values: ['a', 'b']
};
const clone1 = { ...obj };
obj.id = "123";
obj.values[0] = "c";
console.log("obj->", obj);
console.log("clone1->", clone1);

(3)ES6的Object.assign()执行的是浅拷贝

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值