在前端项目开发中,很多情况下都是在操作数据,如果碰到复杂的数据,操作起来就比较困难了。
例如:vue
中数据是双向绑定的,页面显示依赖vue
实例中的数据驱动,当页面中输入新的数据后,vue
实例中的数据也发生了改变。若要将vue
实例中的数据当做参数发送请求的时候,接口所需数据跟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
键,且值为女
。当对象的属性的值是引用类型数据的时候,浅拷贝会将这个引用类型数据的地址也拷贝过来,也就是说没有完全的形成一个新对象,还是跟原对象有些关联。这就是浅拷贝。
数组浅拷贝
Array系统构造函数原型上的concat和slice方法
concat方法
var arr = [1,2,3];
var brr = arr.concat()
arr.push(4);
console.log(arr);
console.log(brr);
此时的brr
跟原来的arr
是一模一样的,改变了arr
后,brr
也是没有发生改变的,因为brr
的数据空间是一个新的地址。
slice方法
var arr = [1,2,3];
var brr = arr.slice();
arr.push(4);
cosnole.log(arr);
cosnole.log(brr);
此时的brr
跟原来的arr
是一模一样的,改变了arr
后,brr
也是没有发生改变的,因为brr
的数据空间是一个新的地址。
注意:如果数组中的数据有引用类型数据,上面两个方法对于数组的拷贝,会将这个引用类型数据的地址也拷贝出来。
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); // 张三 李四
调用函数得到的新数据和原来的数据互不影响。
递归深拷贝
总之,赋值是赋值,拷贝是拷贝。
浅拷贝,当对象或数组中的数据都是基本数据类型的时候,两个数据之间完全是独立的,如果对象或数组中的值是引用类型的时候,里面是引用类型的值,还是会保持共同的内存地址;
深拷贝出来的两个数据是完全独立的。
网友评论