以raect state为例,探索JavaScript对象的深拷贝(上)篇已经探讨了state的更新问题,那么真正要引出的还是JavaScript对象的深拷贝问题
- 基础知识
javascript中,内存空间分为栈(stack)、堆(heap)、池(一般也会归类为栈中)。 其中栈存放变量,堆存放复杂对象,池存放常量。
Number,String,Null,Undefined,Boolean
这五种js基础数据类型,是存放在栈内存的,除此之外,引用数据类型的值是保存在堆内存中的,多层级的数组(对象) 都只是引用
最简单的例子就是
- 例1
var a = 20;
var b = a;
b = 30;
console.log(a)// a:20
- 例2
var a = {i:0};
var b = a;
b.i = 1;
console.log(a.i)// a.i:1
例2中对b的赋值只是一个引用,b引用a在堆内存中的地址,所以改变b.i 会 改变a.i的值,因为他们在同一个内存地址
清楚了js的内存分配机制之后,再回过头来看原来的问题
之前由于在子组件中不经意间改变了props中的值导致调用函数更新父组件的状态后,没有重新render,不难推断出,子组件从props获得的那个值与父组件中的state值,指向的是同一个内存空间
- 浅拷贝
由于数组其实是一种特殊的object,这里只探讨object,大体方法思路是一样的。
首先声明两个obj,A属性是复杂对象,B属性是基础数据类型
- 方法1 遍历对象
function copy (obj) {
let newObj = {};
for (let item in obj ){
newObj[item] = obj[item]
}
return newObj;
}
obj2 = copy(obj1);
obj2.A.b = 123123123;
obj2.B = 666666666;
console.log(obj1);
这个方法,只能遍历一层,并且很显然,修改obj2的B属性是不会影响obj1的B属性的,但由于A属性是复杂对象,所以 修改obj2的A属性是会影响obj1的A属性的 。
- 方法2 拓展运算符 (ES6)
obj2 = {...obj1};
obj2.A.b = 123123123;
obj2.B = 666666666;
console.log(obj1);
方法2同方法1一样,碰见对象就凉凉
- 方法3 Object.assign()
obj2 = Object.assign({}, obj1);
obj2.A.b = 123123123;
obj2.B = 666666666;
console.log(obj1);
Object.assign() 可以将多个对象进行合并操作,若源对象上有改属性,则会被覆盖,没有则添加,最后返回一个合并后的对象, String
类型和 Symbol
类型的属性都会被拷贝。 并且 Object.assign
不会跳过那些值为 null
或 undefined
的源对象。 但依然是浅拷贝
- 深拷贝
通常情况下,我们希望在改变新的数组(对象)的时候,不论是改变的基础数据类型还是复杂对象,都不改变原数组(对象),那么可以对 数组(对象) 进行深拷贝
- 方法1 JSON.parse(JSON.stringify(obj))
obj2 = JSON.parse(JSON.stringify(obj1));
obj2.A.b = 123123123;
obj2.B = 666666666;
console.log(obj1);
JSON.stringify() 和JSON.parse() 互转可以实现深拷贝,这个可能会影响性能,某些特殊情况下,遇到一些特殊的数据类型,会失效,可以作为一种方法记在心中
- 方法2 递归
function deepCopy(o) {
var isArray = o instanceof Array;
var isObject = o instanceof Object;
if (!isObject) return o;
var n = (isArray ? [] : {});
for (var k in o) n[k] = deepCopy(o[k]);
return n;
}
obj2 = deepCopy(obj1);
obj2.A.b = 123123123;
obj2.B = 666666666;
console.log(obj1);