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

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

总结:

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

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

相关推荐
辻戋1 小时前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保1 小时前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun2 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp2 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.3 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl5 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫7 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友7 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理9 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻9 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js