1、基础认识
对于js的对象的深拷贝和浅拷贝,必须先提到的是JavaScript的数据类型。Javascript的数据类型分为两类:基本数据类型和引用数据类型 。
Javascript有五种基本数据类型(也就是简单数据类型),它们分别是:Undefined,Null,Boolean,Number和String
,并且基本类型存放在栈内存。还含有一种复杂的数据类型(也叫引用类型)存放在堆内存,就是对象Object和Array
。堆内存用于存放由new创建的对象,栈内存存放一些基本的类型的变量和对象的引用变量。
JS 中的浅拷贝与深拷贝,只是针对引用数据类型(Object,Array)的复制问题。
- 对于基本数据类型
他们的值在内存中占据着固定大小的空间,并被保存在栈内存中。当一个变量向另一个变量复制基本类型的值,会创建这个值的副本。
var a = 1;
var b = a;
b = 2;
console.log(a); //1
上面的代码中,a是基本数据类型(Number), b是a的一个副本,它们两者都占有不同位置但相等的内存空间,只是它们的值相等,若改变其中一方,另一方不会随之改变。
- 对于复杂数据类型
复杂的数据类型即引用类型,它的值是对象,保存在堆内存中,包含引用类型值的变量实际上包含的不是对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针地址而已,因此两个变量最终都指向同一个对象。
var obj1 = {
name:'zhang san',
age: 20
}
var obj2 = obj1;
obj2.name = 'li si';
obj2.job = 'engineer';
console.log(obj1); //{name: "li si", age: 20, job: "engineer"}
console.log(obj2); //{name: "li si", age: 20, job: "engineer"}
我们可以看到obj1赋值给obj2后,但我们改变其中一个对象的属性值,两个对象都发生了改变,根本原因就是obj1和obj2两个变量都指向同一个指针,赋值时只是复制了指针地址,它们指向同一个引用,所以当我们改变其中一个的值就会影响到另一个变量的值。
以上内容还没有涉及到深拷贝和浅拷贝,只作为背景介绍。对于复杂数据类型而言,简单的 ‘=’ 赋值显然会带来不必要的麻烦,那么该如何解决呢?
2、深拷贝和浅拷贝
深拷贝和浅拷贝的区别
浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制。接下来举例介绍:浅拷贝的实现:
对于数组而言,最简单的可以用slice()和concat()方法实现浅拷贝:var arr1 = [1, 2, 3]; var arr2 = arr1.slice(); arr2[0] = 0; console.log(arr1);//[1,2,3] console.log(arr2);//[0,2,3] var arr3 = [4,5,6]; var arr4 = arr3.concat(); arr4[0] = 0; console.log(arr3);//[4,5,6] console.log(arr4);//[0,5,6]
另外可以用ES6扩展运算符实现数组的浅拷贝:
var arr1 = [1,2,3]; var arr2 = [...arr1]; arr2[0] = 0; console.log(arr1);//[1,2,3] console.log(arr2);//[0,2,3]
或ES6的Array.from()实现数组的浅拷贝:
var arr1=[1,2,3]; var arr2=Array.from(arr1); arr2[0] = 0; console.log(arr1); //[1,2,3] console.log(arr2); //[0,2,3]
或者是通过循环来实现浅拷贝:
var arr1=[1,2,3,4]; var arr2=[]; for(var i=0; i<arr1.length; i++){ arr2[i]=arr1[i]; } arr1.push(5); arr2.push(6); console.log(arr1); //[1,2,3,4,5] console.log(arr2); //[1,2,3,4,6]
接下来是对象浅拷贝的实现方法:
可以用ES6扩展运算符实现对象的浅拷贝:var obj1 = { name:'zhang san', age: 20 } var obj2 = {...obj1}; obj2.name = 'li si'; console.log(obj1);//{name: "zhang san", age: 20} console.log(obj2);//{name: "li si", age: 20}
也可以用ES6的Object.assign()实现对象的浅拷贝,
Object.assign是ES6的新函数。Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
var obj1 = { a: 1, b: 2, c: 3 };
var obj2 = Object.assign({}, obj1);
obj2.b = 0;
console.log(obj1);//{a: 1, b: 2, c: 3}
console.log(obj2);//{a: 1, b: 0, c: 3}
或者通过循环实现对象的浅拷贝:
//浅拷贝
var obj1={
a:10
}
function copy (obj) {
//复制关系,而不是引用关系
var newOBJ={};
for (var k in obj) {//循环复制所有属性,可以称为浅拷贝或者叫浅克隆
newOBJ[k]=obj[k];
};
return newOBJ;
}
var obj2=copy(obj1);
obj2.a=20;
console.log(obj1.a);//10
另外,使用ES5的Object.create()
可以实现对象的浅拷贝:
var obj2 = Object.create(obj1);
浅拷贝解决了上面介绍的 ’ = ’ 赋值存在的问题,但是浅拷贝存在问题:只能拷贝一层。
//举例验证,例1:for循环实现对象的浅拷贝
var obj1={
a:{b:10}
}
function copy (obj) {
var newOBJ={};
for (var k in obj) {
newOBJ[k]=obj[k];
};
return newOBJ;
}
var obj2=copy(obj1);
obj2.a.b=20;
alert(obj1.a.b);//20
//es6实现对象的浅拷贝:
var obj3 = {
a:{b:10}
}
var obj4 = {...obj3};
obj4.a.b = 20;
console.log(obj3);//a:{b: 20}
console.log(obj4);//a:{b: 20}
//slice()实现数组的浅拷贝:
var arr1 = [1,2,{a:2}];
var arr2 = arr1.slice();
arr2[2].a = 0;
console.log(arr1);//[1,2,{a:0}]
console.log(arr2);//[1,2,{a:0}]
//for循环实现数组的浅拷贝:
var arr1=[1,2,3,{a:6}];
var arr2=[];
for(var i=0; i<arr1.length; i++){
arr2[i]=arr1[i];
}
arr2[3].a = 0;
console.log(arr1);//[1,2,3,{a:0}]
console.log(arr2);//[1,2,3,{a:0}]
//不在一一举例
深拷贝:解决了浅拷贝只能拷贝一层的问题。
深拷贝的实现:用递归实现深度拷贝!
下面的代码用递归实现了数组和对象的深度拷贝:
function clone(Obj) {
var buf;
if (Obj instanceof Array) {
buf = []; //创建一个空的数组
var len = Obj.length;
for(var i = 0;i<len;i++){
buf[i] = clone(Obj[i]);
}
return buf;
}else if (Obj instanceof Object){
buf = {}; //创建一个空对象
for (var k in Obj) { //为这个对象添加新的属性
buf[k] = clone(Obj[k]);
}
return buf;
}else{
return Obj;
}
}
//举例验证:对象
var obj1={
a:{b:10}
}
var obj2=clone(obj1);
obj2.a.b=20;
alert(obj1.a.b);//10
//数组:
var arr1 = [1,2,3,{a:6}];
var arr2 = clone(arr1);
arr2[3].a = 0;
console.log(arr1);//[1,2,3,{a:6}];
console.log(arr2);//[1,2,3,{a:0}];
最后,介绍一种简单粗暴的方式,一行代码实现深拷贝:用JSON.stringify
把对象、数组转成字符串,再用JSON.parse
把字符串转成新的对象、数组。
var obj2 = JSON.parse(JSON.stringify(obj1))
。
网上有关JS深浅拷贝的解释不太一致,有文章把浅拷贝当做深拷贝,并且这样的文章还不少,用《阅后即瞎》的经典台词做结束语吧,祝您心明眼亮。