今天有人问到了JS中的浅拷贝和深拷贝,今天索性来写写,那里写的不好还望大佬可以批评指正,好了好不多说步入正题。
深入了解——数据在JS中的储存机制(不想了解的直接看下面的深拷贝和浅拷贝)
1.了解存储机制
- 在JS中对于数据的储存分为两大类 基本数据类型(简单数据类型) 和 引用数据类型(复杂数据类型);
- 基本数据类型 例如: Number String undefined null Boolean Symbol BigInt 等
- 引用数据类型 例如:Function Array Object Date Math 等
- 存储数据的容器分为 栈内存 和 堆内存 ;
- 栈内存:只允许在一段进行插入或删除的线性表,是一种先进后出的数据结构(了解就好);
- 堆内存:基于散列算法的数据结构(了解就好);
- 基本数据类型是直接在栈内存中占据一段空间直接存储的(把变量名和值【内容】直接存储在栈内存);
- 引用数据类型 是把值(内容)存储在堆内存中,然后在栈内存中占据一段空间存储变量名和在堆内存中内容的地址;
2.代码示例(如果不理解代码,请看完这些,再看下图示就理解了)
基本数据类型:
var a=1;
var b=a;
a=2;
console.log(b); // 1
因为基本数据类型在栈内存中是线性储存,变量名直接对应这个值如(a:1),当把a赋值给b,则等于把1赋值给b,在栈内存中存储为( b:1);
引用数据类型
var obj1={
name:"小破船借箭",
age:18
};
var obj2=obj1;
obj1.age=24;
console.log(obj1);//{name:"小破船借箭",age:24}
console.log(obj2);//{name:"小破船借箭",age:24}
因为引用数据类型会把内容存储在堆内存中,而把内容对应的堆内存的地址和变量名存储在栈内存中如 (obj1:obj1内容地址),当把obj1赋值给obj2时实际是把obj1的地址赋值给了obj2 如(obj2:obj1内容地址),所以在更改obj1中的属性时,打印obj2得到的值与obj1的值相同。
3.画图释义+解析
基本数据类型
因为基本数据类型在栈内存中是线性储存,变量名直接对应这个值如(a:1),当把a赋值给b,则等于把a对应的值也就是1赋值给b,所以在栈内存中存储为( b:1);
引用数据类型
因为引用数据类型会把内容存储在堆内存中,而把内容对应的堆内存的地址和变量名存储在栈内存中如 (obj1:obj1内容地址),当把obj1赋值给obj2时实际是把obj1对应的值也就是obj1的内容地址赋值给了obj2 如(obj2:obj1内容地址),这时它们指向相同,属性和方法都可以公用 ,所以在这里更改obj1中的属性时,obj2拿到的属性也是更改后的。
4.JS中为什这么储存
- 基本数据类型相对比较稳定,而且对内存的占用也比较少,所以栈内存的处理速度相对来说要比堆内存要快;
- 引用数据类型内容的大小是动态并且无限的,直接根据引用获取,存储方式无序;
浅拷贝
什么是浅拷贝?
浅拷贝简单来说 拷贝的如果是基本数据类型就是直接拷贝值,如果是引用数据类型就是拷贝的引用地址。(不理解这句的请看上面的JS存储机制)
代码示例一 (经典案例)
var obj1={
name:"小破船借箭",
age:18
};
var obj2=obj1;
console.log(obj2);//{name:"小破船借箭",age:18} 没修改值之前打印的是age是18
obj1.age=24;
console.log(obj1);//{name:"小破船借箭",age:24} obj1被修改后的结果
console.log(obj2);//{name:"小破船借箭",age:24} 修改值之后打印的是age是24 与 obj1相同
弱弱的说句,这里不懂的请上移看下JS存储机制(谢谢)
代码示例二 (经典案例扩展)
var obj1 = {
age: 18,
name: "小破船借箭",
specialty:{
name:'游泳',
beginAge:10
},
like:['旅游','看妞']
};
var obj2 = {};
//写的一个拷贝方法
function shallowCopy(obj,targetObj){
for (let key in obj){
targetObj[key] = obj[key];
}
}
//把obj1拷贝给obj2
shallowCopy(obj1, obj2);
console.log(obj2);
//没修改之前的值{ age: 18,name: "小破船借箭",specialty:{name:'游泳', beginAge:10},like:['旅游','看妞']}
obj1.like.push("睡觉");
console.dir(obj1);
//修改之后obj1的值{ age: 18,name: "小破船借箭",specialty:{name:'游泳', beginAge:10},like:['旅游','看妞','睡觉']}
console.log(obj2);
//修改之后obj2的值{ age: 18,name: "小破船借箭",specialty:{name:'游泳', beginAge:10},like:['旅游','看妞','睡觉']}与obj1的值相同
obj1.age=24;
//再次更改obj1的属性
console.dir(obj1);
//修改之后obj1的值{ age: 24,name: "小破船借箭",specialty:{name:'游泳', beginAge:10},like:['旅游','看妞','睡觉']}
console.log(obj2);
//修改之后obj2的值{ age: 18,name: "小破船借箭",specialty:{name:'游泳', beginAge:10},like:['旅游','看妞','睡觉']}与obj1的值不相同
代码解析:这段代码中,有人会发现第二次更改obj1的属性值,obj2的属性值没有发生变化。那是因为在这段代码中,我们使用到了for···in循环,那么我们对属性一对一赋值时那么name属性和age属性是不是就是基本数据类型,基本数据类型是在栈内存中直接储存对应的值,所以specialty属性和like属性就是引用数据类型,引用数据类型是在堆内存中储存对应的引用地址,所以第二次更改,obj2不受影响。
深拷贝
什么是深拷贝?
深拷贝就是把一个对象(引用数据类型)里面包含的属性和方法都一个个找出来(包括多层嵌套的属性何方法),然后在另一个对象里面一个个对应的在堆内存中储存起来;
代码示例一 (脑残方法)
var obj1={
name:"小破船借箭",
age:18
};
var obj2={
name:obj1.name,
age:obj1.age
};
obj1.age=24;
console.log(obj1);//{name:"小破船借箭",age:24} age被修改后
console.log(obj2);//{name:"小破船借箭",age:18} 与obj1不相同,obj1更改obj2不被影响
代码示例二 (把代码转JSON字符串)
var obj1={
name:"小破船借箭",
age:18
};
var obj2=JSON.parse(JSON.stringify(obj1));
obj1.age=24;
console.log(obj1);
console.log(obj2);
这个方法确实好用,不过是存在一些缺点的:
- 比如undefined、Function和RegExp等是没有不能被转JSON的
- 使用JSON进行深拷贝后,这个对象原来的构造函数(constructor),会变成Object;
代码示例三 (递归拷贝)
var obj1 = {
age: 18,
name: "小破船借箭",
specialty: {
name: '游泳',
beginAge: 10
},
like: ['旅游', '看妞']
};
var obj2 = {};
function deepClone(initObj, targetObj) {
for (let key in initObj) {
let item = initObj[key];
if (item instanceof Array) {
targetObj[key] = [];
deepClone(item, targetObj[key]);
} else if (item instanceof Object) {
targetObj[key] = {};
deepClone(item, targetObj[key]);
} else {
targetObj[key] = initObj[key];
}
}
}
deepClone(obj1, obj2);
obj1.specialty.name="爬山";
console.log(obj1);
//{ age: 18,name:"小破船借箭",specialty:{name:'爬山', beginAge:10},like:['旅游','看妞']} 修改后obj1的值发生变化
console.log(obj2);
//{ age: 18,name:"小破船借箭",specialty:{name:'游泳', beginAge:10},like:['旅游','看妞']} 修改后obj2的值与obj1的值不相同
代码解析:在浅拷贝中我们使用了for···in循环复制了obj1对象的第一层,在这里我们是使用for···in最递归循环,判断当前对象的key是什么类型的,如果是Object或者Array,我们就把当前遍历到的key的属性值再传给该函数,依次循环,知道找到最里层为止。这样我们就等于为这个新的对象开辟了新的空间进行储存了。
后续再更新别的方法!!!
.
.
.
谢谢您的阅读很高兴,如果有什么不对的地方还望批评指正。
既然看到了这里,如果有帮助的话,希望给小破船点个赞吧。谢谢