一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第15天,点击查看活动详情。
深浅拷贝的区别
一句土话总结:把a的值赋值给b,改变b的值,a如果跟着变了,那就是浅拷贝,否则就是深拷贝。
看一段代码:
var a=12;
var b=a;
b=15;
console.log(a,b);//12 15
var obj1={name:'静静'};
var obj2=obj1;
obj2.name='丽颖';
console.log(obj1.name,obj2.name); //丽颖 丽颖
可以看到基本类型,新变量b改变值,不影响原始变量a的值,因为是按值访问的,因此彼此是独立的互不影响的,所以对于基本类型来说浅拷贝深拷贝的没啥意义;而obj1和obj2属于引用类型,obj2值改变后,obj1也跟着改变了,因为,引用类型是按地址访问的,并且obj1和obj2他们的引用的地址是同一个,所以互相影响。
因为对于基本类型来说浅拷贝深拷贝没啥意义,所以深浅拷贝主要针对的引用类型来说一下
官方点详细的总结:
浅拷贝:只赋值对象的第一级属性值,如果对象的第一级属性中又包含引用类型的属性值,则只复制地址。
浅拷贝的问题:如果对象中又包含引用类型的属性值,则导致克隆后,新旧对象依然共用一个引用类型的属性值(也可以说成地址)。
结果:任意一方修改了引用类型的对象内容,都会导致另一方同时受影响。
深拷贝:不但复制对象的第一级属性值,而且,即使对象中又包含引用类型的属性值,深拷贝也会继续复制内嵌类型的属性值。
结果:克隆后,两个对象彻底再无瓜葛互不影响。
浅拷贝
- Object.assign(target,sourse)可实现
var obj1={name:'静静',son:{name:'小明'}};
var obj2=Object.assign({},obj1);
obj2.name='丽颖';
obj2['son'].name='小涛'
console.log(obj1,obj2);
看输出:
只能实现一级的深拷贝,一级的值互不影响,但是一级以上的就不行,比如一级属性值是一个引用类型对象,就依旧会影响原始值,因为复制的是一个地址,原始值对应的属性值和新对象对应的属性值,他们的引用的地址是同一个,依旧互相影响着,所以这也属于浅拷贝。
怎么实现深拷贝
- JSON.pase(JSON.stringfy())
var obj1={name:'静静'};
var obj2=JSON.parse(JSON.stringify(obj1));
obj2.name='丽颖';
console.log(obj1.name,obj2.name); //静静 丽颖
JSON.stringfy(), 将对象先转成字符串,JSON.parse(),再将JSON字符串转成对象。
但是有个缺点:无法深拷贝undefined值和内嵌函数。
加个undefined和内嵌函数试试,如下:
var obj1={name:'静静',son:{name:'小明'},age:undefined,value:()=>console.log(this.name)};
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.name='哈哈'
console.log(obj1, obj2);
打印如下:
age和value并没有被拷贝进来。
解决:利用递归
- 递归 我们自定义一个递归拷贝函数(适用于任何类型的数据)
function deepClone(target) {
//定义一个变量,准备接新副本对象
let newObj;
//如果是一个引用类型
if (typeof target === 'object') {
//如果是个数组
if (Array.isArray(target)) {
//将新副本赋值为空数组,并遍历
newObj = []
for (let item in target) {
//递归拷贝数组中的每一项
newObj.push(deepClone(target[item]));
}
}
//判断当前值是null,直接赋值为null
else if (target === null) {
newObj = null;
}
//判断当前值是一个正则表达式对象,则直接赋值
else if (target.constructor === RegExp) {
newObj = target;
}
//否则为一个普通的对象,直接for in循环递归遍历复制对象中的每个属性值
else {
newObj = {};
for (var item in target) {
newObj[item] = deepClone(target[item])
}
}
}
//如果不是引用类型而是基本类型,那么直接赋值
else{
newObj=target;
}
//返回最终结果newObj
return newObj;
}
深拷贝原理:
先定义一个变量,去接新副本对象,然后去判断类型,是数组的话就继续判断每个当前值的类型,
最后把每个值复制到新对象里来;如果是null的话,就会直接赋值为为null,
因为typeof null也是object类型,如果类型是RegExp,直接赋值为原始对象target,
以上都不是的话,那就是普通对象,让新副本的属性名跟原始对象target的属性名同名,
新副本的属性值跟原始对象target的属性值相同,
如果target原属性值不是原始数据,会再次进行类型判断,最后赋值。
亲测一下这个方法:
function deepClone(target) {
//定义一个变量,准备接新副本对象
let newObj;
//如果是一个引用类型
if (typeof target === 'object') {
//如果是个数组
if (Array.isArray(target)) {
//将新副本赋值为空数组,并遍历
newObj = []
for (let item in target) {
//递归拷贝数组中的每一项
newObj.push(deepClone(target[item]));
}
}
//判断当前值是null,直接赋值为null
else if (target === null) {
newObj = null;
}
//判断当前值是一个正则表达式对象,则直接赋值
else if (target.constructor === RegExp) {
newObj = target;
}
//否则为一个普通的对象,直接for in循环递归遍历复制对象中的每个属性值
else {
newObj = {};
for (var item in target) {
newObj[item] = deepClone(target[item])
}
}
}
//如果不是引用类型而是基本类型,那么直接赋值
else{
newObj=target;
}
//返回最终结果newObj
return newObj;
}
var obj1={name:'静静',son:{name:'小明'}};
var obj2=deepClone(obj1);
obj2.name='丽颖';
obj2['son'].name='小涛'
console.log(obj1,obj2);
输出:
我们可以看到:
obj2里面的第一级name属性值改变了,并且第一级的属性son它的值是一个引用类型对象,它也改变了,但是,最后最终也没有影响原始变量obj1的值。
我们再试试,undefined和内嵌函数是否能被拷贝进来:
function deepClone(target) {
//定义一个变量,准备接新副本对象
let newObj;
//如果是一个引用类型
if (typeof target === 'object') {
//如果是个数组
if (Array.isArray(target)) {
//将新副本赋值为空数组,并遍历
newObj = []
for (let item in target) {
//递归拷贝数组中的每一项
newObj.push(deepClone(target[item]));
}
}
//判断当前值是null,直接赋值为null
else if (target === null) {
newObj = null;
}
//判断当前值是一个正则表达式对象,则直接赋值
else if (target.constructor === RegExp) {
newObj = target;
}
//否则为一个普通的对象,直接for in循环递归遍历复制对象中的每个属性值
else {
newObj = {};
for (var item in target) {
newObj[item] = deepClone(target[item])
}
}
}
//如果不是引用类型而是基本类型,那么直接赋值
else{
newObj=target;
}
//返回最终结果newObj
return newObj;
}
var obj1={name:'静静',son:{name:'小明'},age:undefined,value:()=>console.log(this.name)};
var obj2=deepClone(obj1);
obj2.name='丽颖';
obj2['son'].name='小涛'
console.log(obj1,obj2);
打印如下:
有被拷贝进来;