js的浅拷贝和深拷贝

文章介绍了JavaScript中浅拷贝和深拷贝的区别,通过实例解释了基本数据类型和引用数据类型在内存中的存储方式。并提供了实现深拷贝的几种方法,包括自定义递归函数、JSON.stringify和JSON.parse、lodash的cloneDeep方法、jQuery的extend函数以及原生的structuredClone方法,同时指出了这些方法的适用场景和潜在问题。

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

简单来说,如果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()执行的是浅拷贝

### 浅拷贝深拷贝的区别 在 JavaScript 中,浅拷贝深拷贝是用于复制对象或数组的两种不同方式。它们的主要区别在于对嵌套结构(如对象中的对象)的处理。 #### 浅拷贝 浅拷贝仅复制对象的一层属性,对于对象中包含的子对象(多级属性),它复制的是引用地址而非创建新的对象。因此,如果原对象中的某个子对象被修改,拷贝后的对象也会受到影响[^2]。 例如: ```javascript const obj = { id: 1, name: 'andy', msg: { age: 18 } }; const o = {}; Object.assign(o, obj); console.log(o); // { id: 1, name: 'andy', msg: { age: 18 } } o.msg.age = 20; console.log(obj.msg.age); // 20 ``` 上述代码中,`Object.assign()` 是一种浅拷贝方法,它复制了 `obj` 的一级属性,但 `msg` 属性是一个对象,因此 `o.msg` `obj.msg` 指向同一块内存区域[^2]。 #### 深拷贝 深拷贝则会递归地复制对象的所有层级属性,确保新对象与原对象完全独立。即使原对象发生修改,也不会影响到深拷贝后的对象。然而,深拷贝通常比浅拷贝更复杂且耗时,特别是在处理复杂的嵌套结构时[^3]。 实现深拷贝的方法之一是使用 jQuery 的 `extend` 方法,并传入 `true` 参数以启用深拷贝功能: ```javascript var array = [1, 2, 3, 4]; var newArray = $.extend(true, [], array); ``` 该方法能够有效地避免因共享引用而导致的数据污染问题[^1]。 ### 实现方式对比 #### 浅拷贝实现 - **`Object.assign()`**:适用于单层对象的复制。 - **扩展运算符 `...`**:语法简洁,同样只复制第一层属性。 - **`for...in` 循环**:手动遍历对象属性并逐个赋值。 示例: ```javascript const source = { a: 1, b: { c: 2 } }; const dest = {}; for (let key in source) { dest[key] = source[key]; } dest.b.c = 3; console.log(source.b.c); // 输出 3 ``` 这表明浅拷贝无法隔离原对象与拷贝对象之间的嵌套引用关系。 #### 深拷贝实现 - **jQuery 的 `$.extend(true, {}, source)`**:支持多级嵌套对象的完整复制。 - **递归函数**:通过递归调用自身来复制每一层属性。 - **第三方库(如 Lodash 的 `_.cloneDeep()`)**:提供优化过的深拷贝算法。 - **JSON 序列化反序列化**:利用 `JSON.parse(JSON.stringify(obj))`,但这种方法不支持函数、undefined 等类型的复制。 示例: ```javascript function deepClone(obj) { if (typeof obj !== 'object' || obj === null) return obj; const copy = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { copy[key] = deepClone(obj[key]); } } return copy; } const original = { a: 1, b: { c: 2 } }; const cloned = deepClone(original); cloned.b.c = 3; console.log(original.b.c); // 输出 2 ``` 此递归实现可以确保每个层级的对象都被独立复制,从而实现真正的深拷贝效果[^3]。 ### 适用场景 - **浅拷贝**适合于不需要完全隔离对象的情况,例如配置项的默认值合并等。 - **深拷贝**适用于需要完全独立副本的情形,如状态快照保存、跨组件数据传递等,以避免副作用数据污染。 此外,在堆内存中开辟新空间为拷贝对象分配独立存储,才能真正实现深拷贝的效果,而不是简单地共享引用地址[^4]。 ---
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值