理解浅拷贝与深拷贝

在JavaScript开发中,理解浅拷贝与深拷贝的区别是非常重要的,尤其是当我们处理引用数据类型(如对象、数组和函数)时。下面,我们将讲解浅拷贝和深拷贝的概念、它们的工作原理和实现方法,并介绍WeakSet和WeakMap的概念。最后,我们将探讨如Lodash这样的第三方库是如何实现深拷贝的。

浅拷贝

这个过程只涉及到对象最表面一层的复制。这就意味着,浅拷贝创建的新对象,在其内部结构方面,始终与原对象保持着一种"隐形的纽带"。即使是新对象,其一些深层次的属性依然紧紧地绑定着原对象,形成一种不可见的联系。因此,每当原始对象中的这些共享属性发生改变时,浅拷贝出的新对象中相应的属性也会实时地受到影响。

浅拷贝的实现可以通过以下方法:

对象的浅拷贝

  1. 使用 Object.assign() 方法:

    ini 复制代码
    const originalObj = { a: 1, b: { c: 2 } };
    const shallowCopyObj = Object.assign({}, originalObj);

    这里,shallowCopyObjoriginalObj 的一个浅拷贝。如果修改 shallowCopyObj.b.c 的值,originalObj.b.c 的值也会改变,因为内部对象是通过引用拷贝的。

  2. 使用对象扩展运算符(...):

    ini 复制代码
    const originalObj = { a: 1, b: { c: 2 } };
    const shallowCopyObj = { ...originalObj };

    Object.assign() 类似,这也是一个浅拷贝。内部对象的更改会影响到原始对象。

  3. 使用 Object.create() 方法:

    ini 复制代码
    const originalObj = { a: 1 };
    const shallowCopyObj = Object.create(Object.getPrototypeOf(originalObj), Object.getOwnPropertyDescriptors(originalObj));

    注意:Object.create() 更常用于设置原型链而不是拷贝对象,但可以用上述方式实现浅拷贝。通常,它不直接用于浅拷贝,因为它创建的是一个新对象,其原型指向传入的对象。

数组的浅拷贝

  1. 使用 concat() 方法:

    ini 复制代码
    const originalArray = [1, 2, { a: 3 }];
    const shallowCopyArray = originalArray.concat();

    这里,shallowCopyArrayoriginalArray 的一个浅拷贝。如果修改 shallowCopyArray[2].a 的值,originalArray[2].a 的值也会改变。

  2. 使用 slice() 方法:

    ini 复制代码
    const originalArray = [1, 2, { a: 3 }];
    const shallowCopyArray = originalArray.slice();

    同样,这也是一个浅拷贝。数组内部对象的更改会影响到原始数组。

  3. 使用数组解构:

    ini 复制代码
    const originalArray = [1, 2, { a: 3 }];
    const shallowCopyArray = [...originalArray];

    这也创建了一个浅拷贝。就像前面的方法一样,对内部对象的修改会反映在原始数组中。

深拷贝

相对于浅拷贝,深拷贝递归拷贝所有层次的属性。这意味着它不仅复制对象的直接属性,还复制每个属性指向的对象,一直递归下去。因此,原始对象和深拷贝对象结构完全相同,但没有任何共享的部分。深拷贝开辟一个新的栈,两个对象完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

使用 JSON.parse(JSON.stringify(obj)) 实现深拷贝

假设我们有一个JavaScript对象,该对象包含各种嵌套的属性,包括数组、嵌套对象和基本数据类型:

css 复制代码
const original = {
    name: "John",
    age: 30,
    details: {
        hobby: "reading",
        address: {
            city: "New York",
            country: "USA"
        }
    },
    tags: ["developer", "reader"]
};

要使用 JSON.parse(JSON.stringify(obj)) 对此对象进行深拷贝,我们首先将对象转换为JSON字符串,然后将该字符串解析回JavaScript对象:

ini 复制代码
const deepCopied = JSON.parse(JSON.stringify(original));

现在,deepCopiedoriginal 的一个深拷贝,这意味着我们可以修改 deepCopied 中的属性而不影响 original 对象:

ini 复制代码
deepCopied.details.address.city = "San Francisco";
console.log(original.details.address.city); // 输出 "New York"

在上面的例子中,修改 deepCopied 对象的 details.address.city 属性不会影响 original 对象的同一属性,证明了深拷贝已经成功。

局限性

虽然 JSON.parse(JSON.stringify(obj)) 是一种便捷的深拷贝实现方式,它也有一些局限性:

  1. 无法复制函数:如果原始对象中包含函数,这些函数不会被复制到新对象中。
  2. 无法处理循环引用:如果原始对象中存在循环引用,这种方法会抛出错误。
  3. 特殊对象问题 :日期对象(Date)、正则表达式(RegExp)、MapSet 等特殊对象类型在深拷贝后可能不会保留其原始结构或类型。
  4. 忽略undefined和symbol属性 :如果对象中包含 undefinedsymbol 类型的属性,在深拷贝过程中这些属性会被忽略。

使用递归手写

scss 复制代码
function deepCopy(obj){
    if(obj === null || typeof obj !== 'object') return obj;
    let objCopy = Array.isArray(obj) ? [] : {};
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            objCopy[key] = deepCopy(obj[key]);
        }
    }
    return objCopy;
}

这个 deepCopy 函数会检查每个属性是否为引用类型,并递归调用自己;如果不是,它会直接复制值。

Lodash 的 _.cloneDeep

Lodash 提供的 _.cloneDeep 方法实现了深拷贝,能够处理各种类型的值,包括数组、对象、Map、Set等,并且它也能处理循环引用的情况。

使用 _.cloneDeep 的示例:

ini 复制代码
const _ = require('lodash');
const object = { 'a': 1, 'b': { 'c': 2 } };
const deepCopy = _.cloneDeep(object);

WeakSet 和 WeakMap

WeakSetWeakMap 是ES6中引入的两种新的数据结构,主要区别于 SetMap 在于它们只持有对象的弱引用,这意味着它们不阻止其引用的对象被垃圾回收。

  • WeakSet 是一个不允许重复的值的集合,但这些值必须是对象。
  • WeakMap 是键值对的集合,但键必须是对象,值可以是任意数据类型。

这些特性使它们成为管理对象私有数据或缓存数据的好工具,而不必担心内存泄漏问题。

处理循环引用

在实现深拷贝时,处理循环引用是非常重要的,以避免无限递归导致的程序崩溃。我们可以创建一个 WeakMap 来跟踪已经被复制过的对象,如下示例所示:

ini 复制代码
function deepCopy(obj, weakMap = new WeakMap()) {
    if (obj === null || typeof obj !== 'object') return obj;
    if (weakMap.has(obj)) {
        return weakMap.get(obj);
    }
    let clone = Array.isArray(obj) ? [] : {};
    weakMap.set(obj, clone);
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            clone[key] = deepCopy(obj[key], weakMap);
        }
    }
    return clone;
}
相关推荐
林涧泣3 分钟前
【Uniapp-Vue3】下拉刷新
前端·vue.js·uni-app
luoganttcc1 小时前
华为升腾算子开发(一) helloword
java·前端·华为
Ciderw2 小时前
MySQL为什么使用B+树?B+树和B树的区别
c++·后端·b树·mysql·面试·golang·b+树
九月十九2 小时前
AviatorScript用法
java·服务器·前端
翻晒时光2 小时前
深入解析Java集合框架:春招面试要点
java·开发语言·面试
Jane - UTS 数据传输系统2 小时前
VUE+ Element-plus , el-tree 修改默认左侧三角图标,并使没有子级的那一项不展示图标
javascript·vue.js·elementui
_.Switch3 小时前
Python Web开发:使用FastAPI构建视频流媒体平台
开发语言·前端·python·微服务·架构·fastapi·媒体
菜鸟阿康学习编程3 小时前
JavaWeb 学习笔记 XML 和 Json 篇 | 020
xml·java·前端
索然无味io4 小时前
XML外部实体注入--漏洞利用
xml·前端·笔记·学习·web安全·网络安全·php
ThomasChan1234 小时前
Typescript 多个泛型参数详细解读
前端·javascript·vue.js·typescript·vue·reactjs·js