js的深拷贝与浅拷贝

前言

深拷贝和浅拷贝是处理对象复制时的两种不同方式。在进入正题前,我们先来简单回顾一下数据类型和常见的数据结构:堆和栈

基本数据类型:Null、Undefined、Number、String、Boolean、Symbol、BigInt

引用数据类型:Function、Array、RegExp、Math、Date、Error、Set、Map等

基本类型值保存在栈中,引用类型值保存在堆中,在栈中保留对象的引用地址,当 JavaScript 访问数据时,通过栈中的引用地址进行访问。故在 JavaScript 中原始类型的赋值会完整复制变量值,而引用类型的赋值则是复制的引用地址

浅拷贝 (Shallow Copy)

浅拷贝只复制对象的第一层属性。如果对象的属性值是基本数据类型(如String、Number、Boolean等),则直接复制值;

如果属性值是引用类型(如Array、Object等),则复制其内存地址,而不是复制实际的值或嵌套的对象。以下都是浅拷贝的实现:

  • Object.assign()
js 复制代码
let origin = { a: '原始字符串', b: { c: 1 } };
let copy = Object.assign({}, origin);
origin.a = '改变后的字符串';
copy.b.c = 2;
console.log(origin); // {a: '改变后的字符串', b: {c: 2}}
console.log(copy); // {a: '原始字符串', b: {c: 2}}
  • 扩展运算符
js 复制代码
let origin = { a: '原始字符串', b: { c: 1 } };
let copy = { ...origin };
origin.a = '改变后的字符串';
copy.b.c = 2;
console.log(origin); // {a: '改变后的字符串', b: {c: 2}}
console.log(copy); // {a: '原始字符串', b: {c: 2}}
  • slice
js 复制代码
let origin = [1, 2, 3, 4];
let copy = origin.slice();
console.log(copy);
  • concat
js 复制代码
let original = [1, 2, 3, 4];
let copy = original.concat();

深拷贝 (Deep Copy)

深拷贝相对于浅拷贝来说,不仅复制了对象本身及其包含的原始类型的值,还复制了所有引用类型的实际值。

这意味着,如果你修改拷贝对象中的一个引用类型的值,原始对象中相应的值不会发生变化,因为它们指向了不同的内存地址。

深拷贝不仅复制对象的第一层属性,还递归复制所有的嵌套对象。这意味着,无论对象有多少层嵌套,深拷贝都会创建所有层次的副本。因此,原始对象和拷贝对象之间不会相互影响。

  • JSON.parse(JSON.stringify(object))
js 复制代码
let original = { a: 1, b: { c: 2 } };
let copy = JSON.parse(JSON.stringify(original));
console.log(copy); // { a: 1, b: { c: 2 } }
JSON.parse(JSON.stringify(NaN))  // null

使用 JSON.stringify() 将对象序列化(转换为JSON字符串),然后使用 JSON.parse() 将字符串解析为新的对象。这种方法不能复制函数和循环引用的对象。

  • 递归拷贝

实现深拷贝的一种方法是递归地复制所有属性。对于每个属性,你可以检查它是否是一个对象,如果是,你再次递归拷贝这个对象;如果不是,直接拷贝其值。

javascript 复制代码
function deepCopy(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    let copy;
    if (Array.isArray(obj)) {
        copy = [];
        for (let i = 0; i < obj.length; i++) {
            copy[i] = deepCopy(obj[i]);
        }
    } else {
        copy = {};
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) { // 检查是否是对象自身具有的属性,因为in会包含原型链上的属性,这里只拷贝自身的!
                copy[key] = deepCopy(obj[key]);
            }
        }
    }
    return copy;
}

这种方法递归地复制对象,但是对于特殊情况(如函数、Date、RegExp、循环引用等)需要特殊处理。

js 复制代码
let original = { a: 1, b: { c: 2 } };
let copy = _.cloneDeep(original);
Lodash 的 cloneDeep 方法是一个非常流行的深拷贝实现,它能够处理各种类型的值,并且能管理循环引用。

在选择方法时,需要根据具体需求来决定。如果你需要快速而简单地复制数据,而且不包含复杂对象(如函数、日期、正则表达式等),JSON的方法可能是最快的。

但如果需要一个更稳健和全面的解决方案,那么递归方法或第三方库会更加适用。

总结:

浅拷贝只复制对象的第一层属性,对于属性值是引用类型的,复制的是引用(内存地址)。

深拷贝递归复制所有层次的属性,创建一个完全独立的副本,原对象和拷贝对象之间不会相互影响。

相关推荐
GesLuck21 分钟前
C#控件开发3—文本显示、文本设值
前端·c#
轻口味27 分钟前
【每日学点鸿蒙知识】屏幕高度、证书签名、深色模式对上架影响、Taskpool上下文、List触底加载更多
前端·华为·harmonyos
webmote1 小时前
Fabric.js 入门教程:扩展自定义对象的完整实践(V6)
运维·javascript·canvas·fabric·绘图
冴羽1 小时前
Solid.js 最新官方文档翻译(12)—— 派生信号与 Memos
前端·javascript·react.js
新中地GIS开发老师2 小时前
25考研希望渺茫,工作 VS 二战,怎么选?
javascript·学习·考研·arcgis·地理信息科学·地信
萧大侠jdeps2 小时前
Vue 3 与 Tauri 集成开发跨端APP
前端·javascript·vue.js·tauri
JYeontu3 小时前
实现一个动态脱敏指令,输入时候显示真实数据,展示的时候进行脱敏
前端·javascript·vue.js
发呆的薇薇°3 小时前
react里使用Day.js显示时间
前端·javascript·react.js
嘤嘤嘤3 小时前
基于大模型技术构建的 GitHub Assistant
前端·github
KeepCatch3 小时前
CSS 动画与过渡效果
前端