前端深拷贝和浅拷贝
在前端攻城狮的工作实际应用中,有很多情况下在处理数据的时候,会用到数据的深拷贝和浅拷贝
例如:vue中数据是双向绑定的,页面显示依赖于从后台获取到的数据,但要将这个数据当做参数发送给另外一个接口的时候,其中有几个字段是多余的,此时,如果将原数据中的字段进行删除,将会造成页面中某些数据无法显示,但是多余数据发送给接口的话,会造成请求失败,此时就要用到深拷贝和浅拷贝。
深拷贝和浅拷贝可以考察出一个人的能力具体如何,例如:基本功、逻辑能力、编码能力......
深拷贝和浅拷贝主要针对于引用类型数据,因为基本数据类型赋值后,改变新数据,不会影响到原来的数据;而引用数据类型赋值后,改变新数据,将会影响到原来的数据,此时应该使用深拷贝和浅拷贝定义出一个跟原数据一样但互不影响的数据。
注意:赋值操作和深拷贝浅拷贝不是一回事。
赋值
基本类型数据的赋值
基本数据类型包括:number
、string
、boolean
、undefined
、null
,他们的赋值相对简单,且赋值后两个变量互不影响。
var a = 10;
var b = a;
a = 20;
console.log(a);
console.log(b);
此时的b是10,因为将a赋值给b,是将a空间中的值复制了一份,放到了b空间中,改变a,只是在改变a空间,对b空间并没有影响。
基本类型赋值引用类型数据的赋值
引用数据类型包括:Array
、Object
,他们的赋值相对复杂,且赋值后两个变量共享一个数据内存空间,改变其中一个,另一个也会发生改变。
var arr = [1,2,3];
var brr = arr;
arr.push(4);
console.log(arr);
console.log(brr);
此时的brr也有4个元素,跟arr是一模一样的,因为将arr赋值给brr,是将arr中存储的数据空间地址复制了一份,放到了brr中,arr和brr共享同一个数据空间,所以改变其中一个的数据,另一个也会发生改变。
引用数据类型的赋值深拷贝和浅拷贝的出现,就是为了解决这个问题。
浅拷贝
浅拷贝是将原数据中所有的数据复制一份,放到新的变量空间中,两个变量不共享一个内存地址。
对象浅拷贝
使用系统提供的构造函数Object上的assign
方法。
语法:
Object.assign({},原对象)
// 返回新对象
例:
var obj = {
name:"张三",
age:12,
gender:"男"
}
var pbj = Object.assign({},obj);
console.log(obj);
console.log(pbj);
此时的pbj跟obj是一模一样的
对象浅拷贝但是obj和pbj两个变量中存储的数据空间地址是不一样的,例:
obj.name = '李四';
obj.age = 20;
console.log(obj);
console.log(pbj);
此时的pbj一点变化也没有,name键的值还是"张三",age键的值还是12,因为obj和pbj不共享数据的内存地址。
拷贝后的地址是不一样的注意:如果对象中有数据的值是引用数据类型,在创建新对象的过程中,会将这个引用数据类型的地址也放到新对象中。
var obj = {
name:"张三",
age:12,
gender:"男",
wife:{
name:"翠花",
age:11
}
}
var pbj = Object.assign({},obj);
obj.wife.gender = "女";
console.log(obj.wife);
console.log(pbj.wife);
此时的pbj的wife键对应的对象中,也有了gender键,且值为"女"。当对象的属性的值是引用类型数据的时候,浅拷贝会将这个引用类型数据的地址也拷贝过来,也就是说没有完全的形成一个新对象,还是跟原对象有些关联。这就是浅拷贝。
浅拷贝中值是引用类型数组浅拷贝
1、Array系统构造函数原型上的concat方法
var arr = [1,2,3];
var brr = arr.concat()
arr.push(4);
console.log(arr);
console.log(brr);
此时的brr跟原来的arr是一模一样的,改变了arr后,brr也是没有发生改变的,因为brr的数据空间是一个新的地址。
2、Array系统构造函数运行上的slice方法
var arr = [1,2,3];
var brr = arr.slice();
arr.push(4);
cosnole.log(arr);
cosnole.log(brr);
此时的brr跟原来的arr是一模一样的,改变了arr后,brr也是没有发生改变的,因为brr的数据空间是一个新的地址。
数组浅拷贝 - slice注意:如果数组中的数据有引用类型数据,上面两个方法对于数组的拷贝,会将这个引用类型数据的地址也拷贝出来。
var arr = [1,2,[3,4]];
var brr = arr.concat();
arr[2].push(5);
console.log(arr[2]);
console.log(brr[2]);
此时brr的下标2数据中,也会多出5。
数组中有引用类型的浅拷贝slice也是一样的
var arr = [1,2,[3,4]];
var brr = arr.slice();
arr[2].push(5);
console.log(arr[2]);
console.log(brr[2]);
此时brr的下标2数据中,也会多出5。
数组中有引用类型的浅拷贝这就是浅拷贝,如果数据中都是基本类型数据,就是完全没有联系的两个数据,但是如果数据中引用类型数据,那两个变量还是有一定的联系。
所谓浅拷贝,也就是说,数组或对象中的值如果是基本类型数据,那拷贝后的数据和原数据是完全没有关联,且互不影响的两个数据,如果数组或对象的值是引用类型数据的话,拷贝后的数组或对象中的引用类型的值跟原数据中的引用类型的值,还是存在共享同一地址的现象。
深拷贝
深拷贝,就是不管原数据中值是什么类型的数据,拷贝后的新数据跟原数据是完全没有关联的。
1、利用json数据和json字符串之间的转换
var obj = {
name:"张三",
age:12,
wife:{
name:"翠花",
age:20
}
}
var str = JSON.stringify(obj);
var pbj = JSON.parse(str);
obj.wife.gender = '女';
console.log(obj.wife);
console.log(pbj.wife);
此时pbj的wife数据中没有gender键,不受obj的影响。
对象深拷贝数组深拷贝
var arr = [1,2,[3,4]];
var str = JSON.stringify(arr);
var brr = JSON.parse(str);
arr[2].push(5);
console.log(arr[2]);
console.log(brr[2]);
此时brr下标2的数据没有5,不受arr的影响。
数组深拷贝2、利用递归遍历对象或数组
function clone(source){
var result;
if(Object.prototype.toString.call(source)==='[object Object]'){
result = {};
}else if(Object.prototype.toString.call(source)==='[object Array]'){
result = []
}else{
return;
}
for(var attr in source){
if(Object.prototype.toString.call(source[attr])==='[object Array]' || Object.prototype.toString.call(source[attr])==='[object Object]'){
result[attr] = clone(source[attr])
}else{
result[attr] = source[attr];
}
}
return result;
}
使用:
var arr = [1,2,{
name:"张三",
age:12,
wife:{
name:"翠花",
age:11
}
}];
var res = clone(arr)
res[0] = 5;
console.log(arr[0],res[0]); // 1 5
res[2].name = '李四'
console.log(arr[2].name,res[2].name); // 张三 李四
调用函数得到的新数据和原来的数据互不影响。
利用递归深拷贝
总之,赋值是赋值,拷贝是拷贝。
浅拷贝,当对象或数组中的数据都是基本数据类型的时候,两个数据之间完全是独立的,如果对象或数组中的值是引用类型的时候,里面是引用类型的值,还是会保持共同的内存地址;
深拷贝出来的两个数据是完全独立的。