深拷贝与浅拷贝:代码世界里的永恒与瞬间

深夜的咖啡厅里,小美对着屏幕上的代码叹气。

"又出 bug 了?" 小帅递来一杯热拿铁。

"浅拷贝害的。" 小美指着屏幕上的数组:"我复制了一份数据,结果用户修改副本时,原数据也跟着变了......" 小帅凑近看了眼代码:"就像你给我发张照片,我修图后原图也被改了?"

"对!但深拷贝不会。" 小美敲下一行 JSON.parse(JSON.stringify(arr)),"它会彻底复制所有层级的数据,像重新捏一个一模一样的泥人,连指纹都独立。" 小帅若有所思:"所以浅拷贝是瞬间的镜像,深拷贝是永恒的守护?"

小美点头:"就像我们的初心 ------ 代码会变,但数据的灵魂值得被温柔以待。" 以上纯属瞎编,如有雷同,纯属巧合。

在计算机中,不同的数据类型会在不同的内存空间进行存储,

基本数据类型

  • 存储位置 :基本数据类型包括NumberStringBooleanNullUndefinedSymbol(ES6 新增)和BigInt(ES2020 新增),它们的值直接存储在栈内存中。

  • 特点:栈内存是一种自动分配和释放的内存空间,是一种线性的数据结构,具有先进后出的特点。基本数据类型的大小是固定的,所以在栈内存中可以直接存储它们的值,访问速度快。例如:

javascript 复制代码
let num = 10;
let str = 'hello';
let bool = true;

引用数据类型

  • 存储位置 :引用数据类型包括ObjectArrayFunction等。当创建一个引用数据类型的变量时,实际上是在堆内存 中分配一块空间来存储对象的内容,然后在栈内存 中存储该对象在堆内存中的地址 。 即引用数据类型的实例存储在堆内存中,而在栈内存中存储的是指向堆内存中该实例的引用
  • 特点:堆内存是一种用于动态分配内存的区域,其大小不固定,可以根据需要动态地分配和释放。引用数据类型的大小是不固定的,因此需要在堆内存中分配空间来存储其内容
javascript 复制代码
let obj = { name: 'John', age: 30 };
let arr = [1, 2, 3, 4, 5];
function add(a, b) { return a + b; }

浅拷贝

javascript 复制代码
//浅拷贝
let a = 1;
let b = a;
a = 2;
console.log(a);
console.log(b);
  • 原理 :在 JavaScript 中,基本数据类型(像 NumberStringBoolean 等)是按值传递的。当你执行 let b = a 时,实际上是把 a 的值复制给了 b。此后,ab 是相互独立的,对 a 进行修改不会影响 b
  • 输出结果a 的值是 2b 的值依然是 1

深拷贝 - 对于一维数组

javascript 复制代码
//深拷贝  一维数组
let x = [1, 2, 3];
let y = [];
//1、展开运算符
//y = []  堆内存中开辟新的空间
y = [...x];
2、for循环
for(let i = 0;i < x.length;i++){
    y[i] = x[i];
}
x = [111, 222, 333];
console.log(x);
console.log(y);
  • 原理 :此代码运用展开运算符 ... 对数组 x 进行了深拷贝。展开运算符会把数组 x 的元素逐一复制到新数组 y 里。这里的复制是对数组元素的复制,并存放进新的内存空间。当你重新给 x 赋值时,y 不会受到影响。
  • 输出结果x 的值是 [111, 222, 333]y 的值是 [1, 2, 3]

深拷贝 - 多层数组嵌套

javascript 复制代码
//深拷贝  多层数组嵌套
let m = [1, 2, 3, [4, 5,]];
//在堆内存  [1,2,3,0x1234] 第二层还是地址
//直接展开深层次还是存放地址

//1、使用JOSON   但是不适用function
let n = JSON.parse(JSON.stringify(m));
m[3][1] = 1000;
console.log(m);
console.log(n);
  • 原理 :这里借助 JSON.stringify() 把对象 m 转换为 JSON 字符串,再使用 JSON.parse() 把 JSON 字符串转换回对象 n。这样做的效果是递归地复制对象的所有属性,进而实现深拷贝。但要注意,JSON.stringify() 无法处理functionSymbol 等特殊类型。
  • 输出结果m 的值是 [1, 2, 3, [4, 1000]]n 的值是 [1, 2, 3, [4, 5]]

下面举例JSON拷贝引用数据类型中含有函数的方法

深拷贝 - 函数处理(JSON 方式)错误使用

javascript 复制代码
//2、如果是函数
//由于JSON 不能拷贝函数
let j = {
    name: 'zhangsan',
    age: 18,
    fn() {
        console.log(this.name);
    }
}
let k = JSON.parse(JSON.stringify(j));
console.log(j);
console.log(k);
  • 原理 :当使用 JSON.stringify() 处理包含函数的对象时,函数会被忽略。所以,对象 k 里不会有 fn 方法。
  • 输出结果j 包含 nameagefn 方法,而 k 只有 nameage 属性。

浅拷贝 - 函数(一层)

javascript 复制代码
//2、 函数  一层 无嵌套
let o = {
    name: 'zhangsan',
    age: 18,
    fn() {
        console.log(this.name);
    }
}
let p = { ...o };
o.fn = function () {
    console.log(this.age);
}
console.log(o);
console.log(p);
o.fn();
p.fn();
  • 原理 :这里我们回归最初使用展开运算符 ... 对对象 o 进行深拷贝。浅拷贝只复制对象的一层属性,如果属性是数组或者对象,复制的只是地址,所以会有影响。但是如果是函数的话,由于修改方式不同,函数的修啊给i相当于重写了这个函数,所以在堆内存中又会重新开辟一块内存,当修改 ofn 方法时,pfn 方法不受影响。
  • 输出结果ofn 方法会输出 agepfn 方法会输出 name

通过上面的诸多铺垫,接下来介绍手写深拷贝函数。

手写深拷贝函数

javascript 复制代码
function deepClone(data) {
    //函数直接返回,因为函数返回后 会再内存中开辟一个空间
    //object------------> 三种情况    Three sThree scenarios对象 数组 null
    if(typeof data === 'object' && data !== null){
        let res = Array.isArray(data)? [] : {};
        for(let j in data){
            if(data.hasOwnProperty(j)){
                res[j]  = deepClone(data[j]);
            }
        }
        return res;
    }else{
        return data;
    }
}
javascript 复制代码
//示例
let object = {
    name: 'zhangsan',
    age: 18,
    fn() {
        console.log(this.name);
    },
    fn1: function () {
        console.log(this.age);
    },
}

let object2 = deepClone(object);
object.fn1 = function () {
    console.log(this.age + 10);
}
console.log(object);
console.log(object2);
object.fn1();
object2.fn1();
  • 原理deepClone 函数是一个递归函数,用于实现深拷贝。如果传入的数据是对象或数组,就会创建一个新的对象或数组,然后递归地复制每个属性。如果传入的数据是基本数据类型或函数,就直接返回。
  • 输出结果 :修改 objectfn1 方法不会影响 object2fn1 方法,objectfn1 方法会输出 age + 10object2fn1 方法会输出 age

代码的世界里,深拷贝是永恒的承诺,而浅拷贝是短暂的相遇

代码的世界里,没有真正的永恒。但我们依然执着于深拷贝 ------ 就像明知人生无常,却依然相信爱情。愿你在每一次复制中,都能守护住最珍贵的初心,就像deepClone函数里的递归,在层层剥离的温柔里,找到属于自己的永恒。

如果您觉得这篇文章对您有帮助,欢迎点赞和收藏,大家的支持是我继续创作优质内容的动力🌹🌹🌹也希望您能在😉😉😉我的主页 😉😉😉找到更多对您有帮助的内容。

  • 致敬每一位赶路人
相关推荐
To_OC5 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC6 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic6 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy6 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志6 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble7 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长8 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯8 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜9 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司9 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能