深夜的咖啡厅里,小美对着屏幕上的代码叹气。
"又出 bug 了?" 小帅递来一杯热拿铁。
"浅拷贝害的。" 小美指着屏幕上的数组:"我复制了一份数据,结果用户修改副本时,原数据也跟着变了......" 小帅凑近看了眼代码:"就像你给我发张照片,我修图后原图也被改了?"
"对!但深拷贝不会。" 小美敲下一行 JSON.parse(JSON.stringify(arr))
,"它会彻底复制所有层级的数据,像重新捏一个一模一样的泥人,连指纹都独立。" 小帅若有所思:"所以浅拷贝是瞬间的镜像,深拷贝是永恒的守护?"
小美点头:"就像我们的初心 ------ 代码会变,但数据的灵魂值得被温柔以待。" 以上纯属瞎编,如有雷同,纯属巧合。
在计算机中,不同的数据类型会在不同的内存空间进行存储,

基本数据类型
-
存储位置 :基本数据类型包括
Number
、String
、Boolean
、Null
、Undefined
、Symbol
(ES6 新增)和BigInt
(ES2020 新增),它们的值直接存储在栈内存中。 -
特点:栈内存是一种自动分配和释放的内存空间,是一种线性的数据结构,具有先进后出的特点。基本数据类型的大小是固定的,所以在栈内存中可以直接存储它们的值,访问速度快。例如:
javascript
let num = 10;
let str = 'hello';
let bool = true;
引用数据类型
- 存储位置 :引用数据类型包括
Object
、Array
、Function
等。当创建一个引用数据类型的变量时,实际上是在堆内存 中分配一块空间来存储对象的内容,然后在栈内存 中存储该对象在堆内存中的地址 。 即引用数据类型的实例存储在堆内存中,而在栈内存中存储的是指向堆内存中该实例的引用。 - 特点:堆内存是一种用于动态分配内存的区域,其大小不固定,可以根据需要动态地分配和释放。引用数据类型的大小是不固定的,因此需要在堆内存中分配空间来存储其内容
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 中,基本数据类型(像
Number
、String
、Boolean
等)是按值传递的。当你执行let b = a
时,实际上是把a
的值复制给了b
。此后,a
和b
是相互独立的,对a
进行修改不会影响b
。 - 输出结果 :
a
的值是2
,b
的值依然是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()
无法处理function
、Symbol
等特殊类型。 - 输出结果 :
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
包含name
、age
和fn
方法,而k
只有name
和age
属性。
浅拷贝 - 函数(一层)
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相当于重写了这个函数,所以在堆内存中又会重新开辟一块内存,当修改o
的fn
方法时,p
的fn
方法不受影响。 - 输出结果 :
o
的fn
方法会输出age
,p
的fn
方法会输出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
函数是一个递归函数,用于实现深拷贝。如果传入的数据是对象或数组,就会创建一个新的对象或数组,然后递归地复制每个属性。如果传入的数据是基本数据类型或函数,就直接返回。 - 输出结果 :修改
object
的fn1
方法不会影响object2
的fn1
方法,object
的fn1
方法会输出age + 10
,object2
的fn1
方法会输出age
。
代码的世界里,深拷贝是永恒的承诺,而浅拷贝是短暂的相遇
代码的世界里,没有真正的永恒。但我们依然执着于深拷贝 ------ 就像明知人生无常,却依然相信爱情。愿你在每一次复制中,都能守护住最珍贵的初心,就像deepClone
函数里的递归,在层层剥离的温柔里,找到属于自己的永恒。
如果您觉得这篇文章对您有帮助,欢迎点赞和收藏,大家的支持是我继续创作优质内容的动力🌹🌹🌹也希望您能在😉😉😉我的主页 😉😉😉找到更多对您有帮助的内容。
- 致敬每一位赶路人