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的方法可能是最快的。

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

总结:

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

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

相关推荐
只_只13 分钟前
npm install sqlite3时报错解决
前端·npm·node.js
FuckPatience17 分钟前
Vue ASP.Net Core WebApi 前后端传参
前端·javascript·vue.js
数字冰雹18 分钟前
图观 流渲染打包服务器
服务器·前端·github·数据可视化
JarvanMo19 分钟前
Flutter:我在网上看到了一个超炫的动画边框,于是我在 Flutter 里把它实现了出来
前端
returnfalse20 分钟前
前端性能优化-第三篇(JavaScript执行优化)
前端·性能优化
yuzhiboyouye25 分钟前
前端架构师,是架构什么
前端·架构
全马必破三27 分钟前
Buffer:Node.js 里处理二进制数据的 “小工具”
前端·node.js
web安全工具库32 分钟前
Linux 高手进阶:Vim 核心模式与分屏操作详解
linux·运维·服务器·前端·数据库
一枚前端小能手37 分钟前
🔥 SSR服务端渲染实战技巧 - 从零到一构建高性能全栈应用
前端·javascript
Komorebi_999937 分钟前
Vue3 provide/inject 详细组件关系说明
前端·javascript·vue.js