基本类型和引用类型
ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值是指那些保存堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。
目前基本类型有:Boolean、Null、Undefined、Number、String、Symbol(ES6),引用类型有:Object、Array、Function。
深拷贝与浅拷贝
我们先看两个简单的案例:
//案例1
var num1 = 1, num2 = num1;
console.log(num1) //1
console.log(num2) //1
num2 = 2; //修改num2
console.log(num1) //1
console.log(num2) //2
//案例2
var obj1 = {x: 1, y: 2}, obj2 = obj1;
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 1, y: 2}
obj2.x = 2; //修改obj2.x
console.log(obj1) //{x: 2, y: 2}
console.log(obj2) //{x: 2, y: 2}
案例1中的值就为基本类型,案例2中的值就为引用类型。案例2中的赋值,会改变之前对象的就是典型的浅拷贝,而不会改变之前对象的就是深拷贝,并且深拷贝与浅拷贝的概念只存在于引用类型。
Array自有方法
slice()方法:
//一维数组:
var arr1 = [1, 2], arr2 = arr1.slice();
console.log(arr1); //[1, 2]
console.log(arr2); //[1, 2]
arr2[0] = 3; //修改arr2
console.log(arr1); //[1, 2]
console.log(arr2); //[3, 2]
//二维数组:
var arr1 = [1, 2, [3, 4]], arr2 = arr1.slice();
console.log(arr1); //[1, 2, [3, 4]]
console.log(arr2); //[1, 2, [3, 4]]
arr2[2][1] = 5;
console.log(arr1); //[1, 2, [3, 5]]
console.log(arr2); //[1, 2, [3, 5]]
可以看到,一维数组arr2的修改没有影响到arr1,而二维数组影响到了,所以slice()方法只能实现一维数组的深拷贝。
具备同等特性的还有:concat、Array.from() 。
Object自有方法
1.Object.assign():
//一维对象
var obj1 = {x: 1, y: 2}, obj2 = Object.assign({}, obj1);
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 1, y: 2}
obj2.x = 2; //修改obj2.x
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 2, y: 2}
//二维对象
var obj1 = {
x: 1,
y: {
m: 1
}
};
var obj2 = Object.assign({}, obj1);
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}}
obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 2}}
console.log(obj2) //{x: 2, y: {m: 2}}
可以看出Object.assign()也只能实现一维对象的深拷贝。
2.JSON.parse(JSON.stringify(obj))
var obj1 = {
x: 1,
y: {
m: 1
}
};
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}}
obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 2, y: {m: 2}}
看起来JSON.parse(JSON.stringify(obj))可以完美的实现深拷贝,但是查询MDN文档后会发现有这样的一段描述:
undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
什么意思呢,我们把刚刚上面那个例子改一下:
var obj1 = {
x: 1,
y: undefined,
z: function add(z1, z2) {
return z1 + z2
},
a: Symbol("foo")
};
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: undefined, z: ƒ, a: Symbol(foo)}
console.log(JSON.stringify(obj1)); //{"x":1}
console.log(obj2) //{x: 1}
可以看到,y,z,a都被忽略了,验证了MDN文档那句话的意思:JSON.parse(JSON.stringify(obj))的使用也是有局限性的,不能深拷贝含有undefined、function、symbol值的对象。不过JSON.parse(JSON.stringify(obj))简单粗暴,已经满足90%的使用场景了。
递归解决
我们发现JS 提供的自有方法并不能彻底解决Array、Object的深拷贝问题,我们可以自己使用递归写一个方法实现深拷贝:
function deepCopy(obj){
let result = {},
keys = Object.keys(obj),
key = null,
temp = null;
keys.map(function(item,index){
key = keys[index];
temp = obj[key];
if(temp && typeof temp === 'object'){
// 如果字段的值也是一个对象则递归操作
result[key] = deepCopy(temp);
}else{
result[key] = temp;
}
});
return result;
}
var obj1 = {
x: {
m: 1
},
y: undefined,
z: function add(z1, z2) {
return z1 + z2
},
a: Symbol("foo")
};
var obj2 = deepCopy(obj1);
obj2.x.m = 2;
console.log(obj1); //{x: {m: 1}, y: undefined, z: ƒ, a: Symbol(foo)}
console.log(obj2); //{x: {m: 2}, y: undefined, z: ƒ, a: Symbol(foo)}
这个递归函数可以完美解决上面那些问题,我们也可以用第三方库:jquery的$.extend和lodash的_.cloneDeep来解决深拷贝。上面虽然是用Object来验证的,但对于Array也同样适用,因为Array也是特殊的Object。