JavaScript之深浅拷贝
文章目录
为啥会有深浅拷贝
栈和堆
栈(stack)是自动分配的内存空间,由系统自动释放
堆(heap)则是动态分配的内存,大小不定也不会自动释放
基本类型(undefined、null、number、string、boolean、symbol)是存放在栈中
引用类型(对象Object、函数Function、数组Array)存放在堆中
//基本类型的复制
let a = 10;
let b = a;
b = 20
console.log(a); //10
console.log(b); //20
//引用类型的复制
let obj1 = {
name: 'jack'
};
let obj2 = obj1;
obj2.name = 'simon';
console.log(obj1.name); //simon
console.log(obj2.name); //simon
为了解决上述引用类型赋值时,一个值指向的对象的改变将会导致另外一个值指向的相同的对象的改变,拷贝分为浅拷贝和深拷贝
浅拷贝
浅拷贝——指在复制对象(也包括数组和函数)时,,只对第一层键值进行复制,若是对象里面还包含其他的对象(数组或函数),就只复制内部对象的地址,即内部对象通过其他的值发生改变时,复制的内部对象也会改变。
数组的浅拷贝也可以通过数组的一些方法来完成,如Array.concat()、Array.slice()
解决方法一
浅拷贝的第一种解决方法就是预先设置一个空对象(数组),通过遍历的方式将原来对象(数组)中的值一一赋值给另一个空对象(数组)
//通过for循环来遍历对象中的键值对来完成浅拷贝
//数组的浅拷贝
let arr1 = [1,2,3];
let arr2 = [];
for(let i in arr1){
arr2[i] = arr1[i];
}
arr2.push(4);
console.log(arr1); //[1,2,3]
console.log(arr2); //[1,2,3,4]
//对象的浅拷贝
let obj1 = {
a: '1',
b: '2',
c: '3',
};
let obj2 = {};
for(let item in obj1){
obj2[item] = obj1[item];
}
obj2.cc = '33';
console.log(obj1); //{a:'1',b:'2',c:'3'}
console.log(obj2); //{a:'1',b:'2',c:'3',cc:'33'}
上述案例中数组和对象经过浅拷贝后,对另一个对象(数组)添加值,并不会影响之前的对象(数组)
//若遇到对象中包含子对象的情况,那么子对象只有地址被复制
//子数组的拷贝
let arr1 = [1,2,[3,4,5]];
let arr2 = [];
for(let i in arr1){
arr2[i] = arr1[i];
}
arr1.push(1);
arr2.push(6);
//改变数组中除子数组外的元素,arr1和arr2互不影响
console.log(arr1); //[1, 2, [3,4,5], 1]
console.log(arr2); //[1, 2, [3,4,5], 6]
//改变子数组中的元素,都会产生影响
arr2[2].push(99999);
console.log(arr1); //[1, 2, [3,4,5,99999], 1]
console.log(arr2); //[1, 2, [3,4,5,99999], 6]
//子对象的拷贝
let obj1 = {
a: '1',
b: '2',
c: '3',
names: {s:'simon', j:'jack'}
};
let obj2 = {};
for(let item in obj1){
obj2[item] = obj1[item];
}
obj1.f = '888';
obj2.d = '666';
//分别改变obj1和obj2的最外层键值,互不影响
console.log(obj1); //{a:'1',b:'2',c:'3',names: {s:'simon', j:'jack'},f:'888'}
console.log(obj2); //{a:'1',b:'2',c:'3',names: {s:'simon', j:'jack'},d:'666'}
//若改变内部的对象时,则一者改变,另外一者也会受到影响
obj2.names.t = 'tom';
console.log(obj1); //{a:'1',b:'2',c:'3',names: {s:'simon', j:'jack',t:'tom},f:'888'}
console.log(obj2); //{a:'1',b:'2',c:'3',names: {s:'simon', j:'jack',t:'tom},d:'666'}
解决方法二——Object.assign()
let obj1 = {
a: '1',
b: '2',
c: '3',
};
let obj2 = Object.assign({}, obj1);
obj2.d = '6';
console.log(obj1); //{a: "1", b: "2", c: "3"}
console.log(obj2); //{a: "1", b: "2", c: "3", d: "6"}
解决方法三——运算符…
let obj1 = {
a: '1',
b: '2',
c: '3',
};
let obj2 = {...obj1};
obj2.d = '6';
console.log(obj1); //{a: "1", b: "2", c: "3"}
console.log(obj2); //{a: "1", b: "2", c: "3", d: "6"}
综上所述,浅拷贝对于数组而言,只要数组中还包含子数组,那么浅拷贝在拷贝子数组时,只会拷贝其地址,而不会深层次的拷贝;对于对象而言,若对象中还包含子对象,那么浅拷贝只能拷贝子对象的地址,若子对象通过原来对象改变了属性,那复制后的子对象也会一起改变。
深拷贝就是用来解决子对象复制的
浅拷贝函数的封装
通过上述案例,可以将浅拷贝函数封装以便随时调用
//浅拷贝函数的封装
function shallCopy(obj1,obj2){
for(let i in obj1){
obj2[i] = obj1[i]
}
}
//test
let arr1 = [1,2,3,5];
let arr2 = [];
shallCopy(arr1, arr2);
arr2.push(888);
console.log(arr1); //[1, 2, 3, 5]
console.log(arr2); //[1, 2, 3, 5, 888]
深拷贝
深拷贝指的是复制对象时会将对象完全拷贝一份,拷贝后的对象与原来对象没有任何关系。即使内部嵌套了对象,也会完全拷贝过来,而不是只拷贝一个地址。
解决方法一—JSON.parse(JSON.stringify(object))
通过
JSON.parse(JSON.stringify(object))
来解决但是该方法存在下列局限性:
- 会忽略 undefined
- 会忽略 symbol
- 不能序列化函数
- 不能解决循环引用的对象
let obj1 = {
a: '1',
b: '2',
c: '3',
names: {s:'simon', j:'jack'}
};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.names.t = 'tom';
console.log(obj1); //{a: "1", b: "2", c: "3", names: {s: "simon", j: "jack", t: "tom"}}
console.log(obj2); ////{a: "1", b: "2", c: "3", names: {s: "simon", j: "jack"}}
//会忽略undefined、symbol,并且无法序列化函数
let obj1 = {
age: undefined,
sex: Symbol('male'),
jobs: function() {},
name: 'simon'
};
let obj2 = JSON.parse(JSON.stringify(a=obj1))
console.log(obj2) // {name: "simon"}
解决方法二—封装深拷贝函数
深拷贝实际上就是浅拷贝函数的递归调用
//封装深拷贝函数
function deepCopy(obj){
//根据obj的类型来决定是创建数组还是对象
let newObj = Array.isArray(obj) ? [] : {};
//判断传入的obj是否为对象
if(obj && typeof obj === 'object'){
for(let key in obj){
//若obj的子元素也是对象,则进行递归操作
if(obj[key] && typeof obj[key] === 'object'){
newObj[key] = deepCopy(obj[key]);
}else{
//若不是对象,就直接赋值
newObj[key] = obj[key];
}
}
}
return newObj;
}
//test
let arr1 = [1,2,3,[4,5,6]];
let arr2 = deepCopy(arr1);
arr2[3].push(888);
console.log(arr1); //[1, 2, 3, [4, 5, 6]]
console.log(arr2); //[1, 2, 3, [4, 5, 6,888]]
解决方法三——其他框架
可以使用其他的框架来实现深拷贝,如lodash,以及jQuery的extend方法