当前位置: 首页 > news >正文

JavaScript对象深浅拷贝及解析

JavaScript对象深浅拷贝及解析

 
下面这段代码使用了 JavaScript 中的对象展开运算符(...),核心作用是创建一个 obj1 的浅拷贝对象 obj2
 

1. 基础用法示例

 
先看一个完整的可运行示例,帮你理解效果:
// 原始对象
const obj1 = {name: "张三",age: 20,hobbies: ["篮球", "游戏"] // 嵌套的引用类型
};// 浅拷贝 obj1 到 obj2
const obj2 = { ...obj1 };// 修改 obj2 的基础属性,不会影响 obj1
obj2.name = "李四";
console.log(obj1.name); // 输出:张三(obj1 不受影响)
console.log(obj2.name); // 输出:李四// 注意:浅拷贝的坑——修改嵌套对象会影响原对象
obj2.hobbies.push("看书");
console.log(obj1.hobbies); // 输出:["篮球", "游戏", "看书"](obj1 被影响了)
console.log(obj2.hobbies); // 输出:["篮球", "游戏", "看书"]

image

2. 关键知识点解释

 
  • 浅拷贝:只复制对象的第一层属性。如果对象里嵌套了数组、另一个对象等 “引用类型”,拷贝的只是它们的内存地址,而非真正的独立数据(这就是上面示例中 hobbies 数组被同时修改的原因)。

与直接赋值的区别

// 直接赋值:obj3 和 obj1 指向同一个内存地址,修改一个都会变
const obj3 = obj1;
obj3.name = "王五";
console.log(obj1.name); // 输出:王五(obj1 被修改)

扩展用法:拷贝时还能新增 / 覆盖属性

const obj2 = { ...obj1, age: 21, gender: "男" };
console.log(obj2.age); // 输出:21(覆盖原属性)
console.log(obj2.gender); // 输出:男(新增属性)

image

3. 如何实现深拷贝(解决嵌套引用问题)

 
如果需要完全独立的拷贝(包括嵌套对象),可以用以下方法:

第一种深拷贝的方法:

 基础版递归深拷贝(核心逻辑)

这个版本只处理普通对象数组,以及基本类型,去掉了特殊类型和循环引用的处理,核心逻辑一目了然,适合理解递归的本质:
/*** 基础版递归深拷贝(仅处理普通对象、数组、基本类型)* @param {*} obj 要拷贝的对象/数组/基本类型* @returns {*} 拷贝后的新值*/
function deepCloneBasic(obj) {// 1. 终止条件:如果是基本类型/null,直接返回(递归出口)if (obj === null || typeof obj !== 'object') {return obj;}// 2. 创建空容器:区分数组和普通对象let cloneObj = Array.isArray(obj) ? [] : {};// 3. 遍历原对象/数组的属性/元素,递归拷贝for (let key in obj) {// 只遍历自有属性(避免遍历原型链上的属性)if (obj.hasOwnProperty(key)) {// 核心:对每个值递归调用深拷贝cloneObj[key] = deepCloneBasic(obj[key]);}}return cloneObj;
}// ============== 测试示例 ==============
// 原始对象(包含嵌套对象和数组)
const obj1 = {name: "张三",age: 20,hobbies: ["篮球", "游戏"],info: {address: "北京"}
};// 深拷贝
const obj2 = deepCloneBasic(obj1);// 验证:修改拷贝后的对象,原对象不受影响
obj2.name = "李四";
obj2.hobbies.push("看书");
obj2.info.address = "上海";console.log("原对象 obj1:", obj1);
// 输出:obj1 = { name: '张三', age: 20, hobbies: ['篮球', '游戏'], info: { address: '北京' } }console.log("拷贝后的 obj2:", obj2);
// 输出:obj2 = { name: '李四', age: 20, hobbies: ['篮球', '游戏', '看书'], info: { address: '上海' } }

以上这版基础的递归拷贝,最主要的是要弄明白在 deepCloneBasic 这个递归深拷贝函数里,每一层递归创建的 cloneObj 为什么不会互相冲突,核心是理解递归过程中变量的作用域问题。

核心原因:函数执行上下文与变量作用域

 
要理解这个问题,关键在于 JavaScript 中函数每次调用都会创建独立的执行上下文,函数内声明的变量(比如 cloneObj)是局部变量,只在当前这次函数调用中有效,不同调用之间互不干扰。
 
结合上面的代码和递归执行过程来拆解:
 

1. 递归调用的执行过程(以 obj1.info 为例)

 
当拷贝 obj1 = { info: { address: "北京" } } 时,递归调用是分层的:
// 第一层调用:处理整个 obj1
deepCloneBasic(obj1) → 创建 cloneObj = {}(空对象,用于存储 obj1 的拷贝)→ 遍历到 key = "info",值是 { address: "北京" }→ 调用 deepCloneBasic(obj1.info)  // 第二层递归// 第二层调用:处理 obj1.infodeepCloneBasic(obj1.info)→ 创建 cloneObj = {}(空对象,用于存储 obj1.info 的拷贝)→ 遍历到 key = "address",值是 "北京"(基本类型)→ 直接返回 "北京",赋值给当前层的 cloneObj["address"]→ 第二层调用结束,返回 { address: "北京" }// 回到第一层→ 把第二层返回的 { address: "北京" } 赋值给第一层的 cloneObj["info"]→ 第一层调用结束,返回完整的拷贝对象

2. cloneObj 不冲突的关键

 
  • cloneObj 是在 deepCloneBasic 函数内部用 let 声明的局部变量,每次调用函数时,都会重新创建一个全新的 cloneObj,和其他调用的 cloneObj 内存地址完全不同。
  • 每一层递归的 cloneObj 只服务于当前层要拷贝的对象 / 数组:
    • 第一层的 cloneObj 负责装 obj1 的所有属性(name、age、hobbies、info);
    • 第二层的 cloneObj 只负责装 obj1.info 的属性(address);
    • 处理数组 hobbies 时,第三层的 cloneObj 是数组 [],只装 ["篮球", "游戏"],和前两层的对象型 cloneObj 也完全独立。
    •  

3. 直观举例:变量的独立性

 
可以用简单代码验证局部变量的独立性:
function test(num) {let local = num; // 每次调用都会新建 localconsole.log("当前 local:", local);if (num > 0) {test(num - 1); // 递归调用}console.log("调用结束,local 仍为:", local);
}test(2);
// 输出:
// 当前 local:2
// 当前 local:1
// 当前 local:0
// 调用结束,local 仍为:0
// 调用结束,local 仍为:1
// 调用结束,local 仍为:2

可以看到,每次递归的 local 都是独立的,修改内层的 local 不会影响外层,你的 cloneObj 原理完全一致。

也就是说:

 
  1. cloneObj 是函数内的局部变量,每次递归调用函数都会创建一个全新的 cloneObj,内存地址独立,互不干扰;
  2. 每一层的 cloneObj 只负责存储当前层要拷贝的对象 / 数组的内容,递归结束后返回给上一层,最终组装成完整的深拷贝结果;
  3. 递归的 “分层处理”+ 局部变量的 “独立作用域”,是 cloneObj 不冲突的核心原因。

第二种深拷贝方法:

JSON.parse(JSON.stringify())(简单场景可用)

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = JSON.parse(JSON.stringify(obj1));obj2.b.c = 999;
console.log(obj1.b.c); // 2 → 不受影响
✅ 优点:简单、一行代码
❌ 缺点:
  • 无法处理函数、undefinedSymbol(会被忽略或转为 null
  • 不能处理循环引用(会报错)
  • 会丢失原型链、DateRegExp 等特殊对象的类型(变成普通对象或字符串)

第三种深拷贝方法:

使用第三方库(生产推荐)

    • Lodash: _.cloneDeep(obj)
const _ = require('lodash');
const obj2 = _.cloneDeep(obj1);

 

 

 

http://www.jsqmd.com/news/275116/

相关文章:

  • sfda
  • [豪の算法奇妙冒险] 代码随想录算法训练营第三十四天 | 62-不同路径、63-不同路径Ⅱ
  • 大数据毕设项目:基于django的电子产品电商平台主数据管理系统(源码+文档,讲解、调试运行,定制等)
  • microblaze是怎么通过把数据通过axi总线给到ip的
  • C++课后习题训练记录Day71
  • 【Android 美颜相机】第十天:YUV420SP和RGB
  • fpga 低频模块和高频模块之间单脉冲信号传输 verilog
  • CAD一键批量标注线长度——CAD c#二次开发
  • 【Android 美颜相机】第十一天:GPUImageFilter解析
  • 换根 DP 简介
  • 【毕业设计】基于机器学习的网络购物平台的智能推荐(源码+文档+远程调试,全bao定制等)
  • 强烈安利8个AI论文软件,本科生搞定毕业论文!
  • 强烈安利8个AI论文软件,本科生搞定毕业论文!
  • 第 470 场周赛Q2——3702. 按位异或非零的最长子序列
  • 文字标注旋转角度设置(防止文字倒立)
  • 储能辅助电力系统调峰的容量需求研究 Matlab代码
  • 咋的,寒假 1 个月学门黑客技术,难道很难吗?
  • 储能辅助电力系统调峰的容量需求研究 Matlab代码
  • 【Matlab】 CRC-8 计算数组Checknum
  • 拒绝“数据搬运工”:PostgreSQL 存储过程与函数实战指南
  • 2026年评价高的镀锌桥架,模压桥架,北方电缆桥架厂家行业优质推荐 - 品牌鉴赏师
  • 吐血推荐!本科生AI论文平台TOP10:开题报告文献综述全搞定
  • 开源版 Claude Code 杀疯了,怒斩 70k+ Star!!
  • 大数据毕设选题推荐:基于django的菜价可视化系统蔬菜销售分析与预测可视化系统【附源码、mysql、文档、调试+代码讲解+全bao等】
  • UVM-build_phase/run_phase的执行顺序及仿真调度
  • AL_ControlRes代码中文注释
  • Jetbrains全家桶自动破解
  • Makefile中 =、:=和 ?=的使用方法
  • 生成式软件制造--AI驱动的软件开发 - 教程
  • C++ 线程互斥锁 lock_guard