什么是深拷贝和浅拷贝
基础回顾
JS中两大数据类型:
基本类型:Undefined、Null、Boolean、Number、String、Symbol
引用类型:Object Array
基本类型就是值类型, 存放在栈(stack)内存中的简单数据段,数据大小确定,内存空间大小可以分配
引用类型, 存放在堆(heap)内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置
为什么要用深拷贝和浅拷贝
下面来看一个实例,分别对两种类型做一个copy处理
let obj = {
name: 'date',
value: '20210422'
}
let obj2 = obj
let obj3 = obj.name
console.log(obj2.value) //20210422
console.log(obj3) // date
// 改变obj2,obj3
obj2.value = '2021年4月22日'
obj3 = '日期'
console.log(obj.value) // 2021年4月22日
console.log(obj.name) // date
复制代码
如上所示,obj2和obj3分别是对一个对象的浅拷贝和深拷贝。改变obj2的值,obj的对应的值也会发生相应的改变,因为obj2是obj的浅拷贝,拷贝的是obj指针指向的地址,因此obj和obj2操作的是同一对象。 而obj和obj3是完全独立的,因为obj3拷贝的是obj的值,而非引用地址。
怎样实现深拷贝和浅拷贝
浅拷贝实现方式
-
“=”赋值
-
Object.assign()
Object.assign是ES6的新函数。Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
Object.assign(target, ...sources)
复制代码
参数:
target:目标对象。
sources:任意多个源对象。
返回值:目标对象会被返回。
var obj1 = { a: {a: "hello", b: 'world'} };
var obj2 = Object.assign({}, obj1);
obj2.a.a = "hi";
console.log(obj1.a.a); // "hi"
复制代码
- 展开运算符…
let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }
复制代码
- Array.prototype.concat()
let arr = [1, 3, {
username: 'kobe'
}];
let arr2 = arr.concat();
arr2[2].username = 'wade';
console.log(arr); //[ 1, 3, { username: 'wade' } ]
复制代码
5.Array.prototype.slice()
let arr = [1, 3, {
username: ' kobe'
}];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr); // [ 1, 3, { username: 'wade' } ]
复制代码
深拷贝实现方式
- 手动赋值
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }
复制代码
- JSON做字符串转换
用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。
var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1);
// { body: { a: 10 } } <-- 沒被改到
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// false
复制代码
这样做是真正的Deep Copy,这种方法简单易用。
但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。
这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝
也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。
var obj1 = { fun: function(){ console.log(123) } };
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun);
// 'function'
console.log(typeof obj2.fun);
// 'undefined' <-- 没复制
复制代码
- 递归拷贝
function deepClone(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) ? [] : {};
arguments.callee(prop, obj[i]);
} else {
obj[i] = prop;
}
}
return obj;
}
var str = {};
var obj = { a: {a: "hello", b: 21} };
deepClone(obj, str);
console.log(str.a);
复制代码
总结
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,两个对象指向同一个地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,两个对象指向不同的地址,因此修改新对象不会影响原对象。
踩坑点
在项目开发中点击编辑会有编辑弹窗跳出,可以对现有的信息进行编辑改变,defaultForm保存的是后端返回的编辑前的值,打开弹窗的可编辑表单双向绑定的是roleForm的值,因此一开始点击编辑弹出弹窗会触发的click事件中将defaultForm的值赋值给了roleForm(!!!!因为此处是浅拷贝,所以改变了roleForm的值后,未点击“确定”按钮,点击“取消”弹窗消失后回显的居然是编辑后的数据,这很明显是BUG!!!!没想到自己不注意的细节踩了这么一个坑,记录一下,学而时习之温故而知新)
参考链接
segmentfault.com/a/119000001…
segmentfault.com/a/119000001…
segmentfault.com/a/119000001…