深拷贝和浅拷贝
一、深拷贝和浅拷贝的概念
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一内存。但深拷贝会另外创造一个一模一样的对象,新旧对象不共享内存,修改新对象不会改到原对象。
浅拷贝:只是拷贝数据的内存地址,而不是在内存中重新创建一个一模一样的对象/数组。
深拷贝:在内存中开辟一个新的存储空间,完完全全的拷贝一个一模一样的对象/数组。
不论是number、string、boolean还是object、array都会被存储在内存中。内存又被分为栈内存和堆内存。 基本数据类型(如:string、number、boolean、null、undefined)会被直接存储到栈内存中。像数组、对象等由多种基本数据类型组成的复杂数据类型,他们的实体内容会被存储到堆内存中。栈内存只会存储他们在堆内存中的一串地址。
二、深拷贝、浅拷贝和赋值的区别
1、赋值是两个对象一起指向一个对象,无论是谁改变了对象里面的内容都会互相影响。
2、浅拷贝和深拷贝都是将拷贝的内容放入新的对象中去,区别在于浅拷贝只会新增一个外层对象来放要拷贝对象的所有内容,所以如果要拷贝的对象里面的属性是对象则拷贝的是对象的引用,而深拷贝则是被拷贝对象包含多少对象,深拷贝就会对应生成多少对象来--对应往里装被拷贝对象里的内容,所有的对象都是独立全新的拷贝一份。
简单来说:
- 赋值操作两个变量指向同一个对象,两者互相影响。
- 浅拷贝新生成一个对象,对象里面的属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址。
- 深拷贝会新生成所有对象(对象里面层层嵌套对象),对象里面的属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),会新生成一个对象将内容拷贝进去。
三、浅拷贝的实现方式
1.Object.assgin()
Object.assgin()方法可以把任意多个源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是Object.assgin()进行的是浅拷贝,拷贝的是对象的引用,而不是对象本身。
var obj = {
a:{
a:'hello',
b:18
}
}
var objData = Object.assgin({},obj);
objData.a.a = 'changed';
console.log(obj.a.a); //changed
如果对象里面的属性不是对象,则进行的是深拷贝。
var obj = {
a:10,
b:20,
c:30
}
var objData = Object.assgin({},obj);
objData.b = 100;
console.log(obj); //{a:10,b:20,c:30}
console.log(objData); //{a:10,b:100,c:30}
2、Array.prototype.concat()
let arr = [1,3,{username:'小赵'}];
let arr2 = arr.concat();
arr2[2].username = '张三';
console.log(arr); //[1,3,{username:'张三'}]
//修改新对象会影响原对象
3、Array.prototype.slice()
let arr = [1,3,{username:'小赵'}];
let arr2 = arr.slice();
arr2[2].username = '张三';
console.log(arr); //[1,3,{username:'张三'}]
//修改新对象同样会影响原对象
关于Array的slice和concat方法补充说明:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中元素的一个数组。
原数组的元素会按照下述规则拷贝:
- 如果该元素是个对象引用(不是实际的对象),slice会拷贝这个对象引用到新的数组里面。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中这个元素也会发生改变。
- 对于字符串、数字和布尔值来说,slice会拷贝这些值到新的数组里面。在别的数组里修改这些字符串、数组或是布尔值,不会影响另一个数组。
例:
let arr = [1,3,{username:'小赵'}];
let arr2 = arr.slice();
arr2[1] = 10
console.log(arr,arr2); //[1,3,{username:'小赵'}] [10,3,{username:'小赵'}]
四、深拷贝实现方式
1、JSON.parse(JSON.stringify())
用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。可以实现数组或对象深拷贝,但不能处理函数。
var obj = {
a:{
b:10
}
}
var objData = JSON.parse(JSON.stringify(obj));
objData.a.b = 20;
console.log(obj); //{a:{b:10}}
console.log(objData); //{a:{b:20}}
console.log(obj === objData); //false
console.log(obj.a === objData.a); //false
JSON.stringify的坑
-
当对象中有时间类型的元素的时候--时间类型会变成字符串类型数据
const obj = { date:new Date() } typeof obj.date === 'object' //true const objCopy = JSON.parse(JSON.stringify(obj)); typeof objCopy.date === 'string' //true -
当对象中有undefined类型或function类型的数据时--undefined和function会直接丢失
const obj = { undef:undefined, fun:()=>{ console.log('000') } } console.log(obj); //{undef:undefined,fun:f} const objCopy = JSON.parse(JSON.stringify(obj)) console.log(objCopy) //{} -
当对象中有NaN、Infinity和-Infinity这三种值的时候--会变成null
1.7976931348623157E+10308是浮点数的最大上限,显示为Infinity
-1.7976931348623157E+10308是浮点数的最小下限,显示为-Infinity
const obj = { nan:NaN, infinityMax:1.7976931348623157E+10308, infinityMin:-1.7976931348623157E+10308, } console.log(obj); //{nan:NaN,infinityMax:Infinity,infinityMin:-Infinity} const objCopy = JSON.parse(JSON.stringify(obj)); console.log(objCopy); //{nan:null,infinityMax:null,infinityMin:null} -
当对象循环引用的时候--报错
const obj = { objChild:null } obj.objChild = obj; const objCopy = JSON.parse(JSON.stringify(obj)); console.log(objCopy);
2、递归拷贝
递归方法实现深度克隆原理:遍历对象、数组知道里面都是基本数据类型,然后再去复制,就是深度拷贝。
var obj = {
a:{
name:'小赵'
},
b:2,
arr:[1,2]
}
var objData = {}
//参数:初始值,完成值
function deepClone(initalObj,finalObj) {
var obj = finalObj || {};
for(var i in initalObj) {
//判断是都引用类型,object,Array的typeof检测都是object
if(typeof initalObj[i] === 'object') {
//递归前,判断是对象还是数字,初始化
obj[i] = (initalObj[i].constructor === Array) ? [] : {};
//递归自己
arguments.callee(initalObj[i],obj[i]);
} else {
//基础类型值 直接复制
obj[i] = inital[i]
}
}
return obj;
}
deepClone(obj,objData);
console.log(objData); //{a:{name:'小赵'},b:2,arr:[1,2]}
3、lodash
该函数库提供了_.cloneDeep用来深拷贝
var _ = require('lodash');
var obj = {
a:1,
b:{
f:{
g:1
}
},
c:[1,2,3]
}
var objData = _.cloneDeep(obj);
console.log(obj.b.f === objData.b.f); //false
4、Object.create()
直接使用var newObj = Object.create(oldObj)可以达到深拷贝的效果。
function deepClone(initalObj,finalObj){
var obj = finalObj || {};
for(var i in initalObj){
var prop = initalObj[i];
if(prop === obj){
continue;
}
if(typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
} else {
obj[i] = prop;
}
}
return obj;
}