我们都知道,JavaScript中有两种数据类型,基本数据类型和引用数据类型。
对于js中的基本数据类型,如number、string、boolean等,我们都是按值访问,因此浅拷贝是对其值的拷贝;对于引用类型,我们都是按引用访问,即保存在变量对象中的地址。通过该地址去访问堆内存里的实际值。因此,对于相对复杂的object类型的数据,如对象、数组等就存在浅拷贝和深拷贝。
浅拷贝是对对象地址的拷贝,并没有开辟新的内存空间,即复制的结果会是两个对象指向同一个地址,修改其中一个的对象属性,另一个对象属性也会发生改变。
var a = 10;
var b = a;
b = 100;
console.log(a); //10
console.log(b); //100
//对象的浅拷贝
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = obj1;
obj2.b = 200;
console.log(obj1); //{a: 10, b: 200, c: 30}
console.log(obj2); //{a: 10, b: 200, c: 30}
深拷贝则会开辟新的内存空间,两个对象对应不同的地址,修改一个对象的属性,并不会改变另一个对象的属性。如下代码所示:
//深拷贝
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 200;
console.log(obj1); // { a: 10, b: 20, c: 30 }
console.log(obj2); // { a: 10, b: 200, c: 30 }
一.浅拷贝的实现:
我们设想一下如何将一个对象里的属性和方法复制到另一个对象里呢?
var person = { name: 'Mary', age: '20'};
var teacher = {sex: 'man'};
function shallowCopy( obj, obj2 ){
var obj2 = obj2 || {};
for( var item in obj ){
obj2[item] = obj[item]
}
return obj2;
}
shallowCopy( person, teacher );
console.log( person );
console.log( teacher );
但我们如果将代码做如下修改,person对象里面还有一个子对象grade,则在复制时浅拷贝只是将子对象的引用传递给新的对象,因此改变任一个的grade属性都会影响到另一个。
2.使用Object.assign() 方法
该方法会遍历一个或多个源对象的所有可枚举(enumerable)的自有键(owned key)并将他们复制(使用=操作符)到目标对象,然后返回目标对象。如果在这个过程中出现同名的属性(方法),后合并的属性(方法)会覆盖之前的同名属性(方法)。
Object.assign(target,source1,source2,source3);
但是 Object.assign() 进行的也是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。 准确的来说,是一级深拷贝。
var obj = {a: {b: 1}};
var shallowClone= Object.assign({}, obj);
shallowClone.a.b = 2;
obj.a.b //2
二.深拷贝的实现
我们需要深拷贝来 解决引用类型的拷贝问题,常见的深拷贝方法有:
1.采用递归的方法去拷贝对象:
var person = { name: 'Mary', age: '20',grade: {English: 100} };
var teacher = {sex: 'man'};
function deepCopy( obj, obj2 ){
var obj2 = obj2 || {};
for( var item in obj ){
// 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(obj[item] === obj2) {
continue;
}
if( typeof obj[item] == 'object'){
obj2[item] = ( obj[item].constructor === Array )?[]:{};
deepCopy( obj[item], obj2[item] )
}else{
obj2[item] = obj[item];
}
}
return obj2;
}
deepCopy( person, teacher );
teacher.name = 'Tom';
teacher.grade.English = 120;
console.log( person );
console.log( teacher );
2.使用JSON方法:
用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。
这种方法虽然简单,但它会抛弃对象的constructor,也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。 并且只能正确处理那些能够被 json 直接表示的数据结构(即JSON安全的对象,也就是可以被序列化为一个JSON字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象)。RegExp对象、function都是无法通过这种方式深拷贝的。
var obj = { grade: { English: 100 }};
var obj2 = JSON.parse( JSON.stringify( obj ));
obj2.grade.English = 110;
console.log( obj.grade.English); //100
console.log( obj2.grade.English); //110
3.使用Object.create()方法:
Object.create(prototype, descriptors)
参数说明:
prototype:必需。 要用作原型的对象。 可以为 null。
descriptors“”可选。 包含一个或多个属性描述符的 JavaScript 对象。 “数据属性”是可获取且可设置值的属性。 数据属性描述符包含 value 特性,以及 writable、enumerable 和 configurable 特性。 如果未指定最后三个特性,则它们默认为 false。 只要检索或设置该值,“访问器属性”就会调用用户提供的函数。 访问器属性描述符包含 set 特性和/或 get 特性。
返回值:一个具有指定的内部原型且包含指定的属性(如果有)的新对象。
{let person = { name: 'Mary', age: '20',grade: {English: 100} };
let teacher = Object.create(person);
teacher.age=11;
teacher.grade.English=199;
console.log(person);
console.log(teacher);
}
由上述结果可知:直接使用Object.create()方法,进行的也是一级深拷贝,原型对象里的耳机属性值会被改变。
因此作如下的修改:
function deepCopy(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i];
// 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
} else {
obj[i] = prop;
}
}
return obj;
}
var person = { name: 'Mary', age: '20',grade: {English: 100} };
var teacher = {sex: 'man'};
deepCopy( person, teacher );
teacher.name = 'Tom';
teacher.grade.English = 120;
console.log( person );
console.log( teacher );
由上述结果可知,我们利用Object.create()方法,达到了深拷贝。