目录
最近遇到一些对象赋值问题,究其根本其实就是深拷贝和浅拷贝的问题,随手记录一下
1. 什么是深拷贝和浅拷贝?
浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝:
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
2. Object.assign()
在说浅拷贝和深拷贝之前,先介绍一个ES6的Object.assign()的方法,主要用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象上。用法如下:
let target = { a: 0 };
let source1 = { b: 1 };
let source2 = { c: 2 };
Object.assign(target, source1, source2);
console.log(target); // 输出 {a: 0, b: 1, c: 2}
ps:Object.assign()的用途很广泛,感兴趣大家可以自行百度。这里就可以用来对象拷贝。大部分资料直接说用来作为浅拷贝,直接贴代码简单介绍下:
// 1.复制对象 Object.assign({}, obj)
let obj = {
name: '张三',
education: {
primary: '第一小学',
middle: '第二中学',
university: '第三大学'
}
};
let copyObj = Object.assign({}, obj);
console.log('----------初始化----------');
console.log('obj.name:', obj.name);
console.log('copyObj.name:', copyObj.name);
console.log('obj.name === copyObj.name:', obj.name === copyObj.name);
console.log('----------原值重新赋值----------');
obj.name = '李四';
console.log('obj.name:', obj.name);
console.log('copyObj.name:', copyObj.name);
console.log('obj.name === copyObj.name:', obj.name === copyObj.name);
console.log('----------拷贝值重新赋值----------');
copyObj.name = '王五';
console.log('obj.name:', obj.name);
console.log('copyObj.name:', copyObj.name);
console.log('obj.name === copyObj.name:', obj.name === copyObj.name);
对应输出结果
可以看到,拷贝后的对象属性和原值在修改时,并不改变彼此的值,看起来就是深拷贝,但是name属性是值类型,可以作为深拷贝,我们接下来换引用类型的education属性测试下:
console.log('----------初始化----------');
console.log('obj.education:', obj.education);
console.log('copyObj.education:', copyObj.education);
console.log('obj.education === copyObj.education:', obj.education === copyObj.education);
console.log('----------原值重新赋值----------');
obj.education.primary = '改后小学'
console.log('obj.education:', obj.education);
console.log('copyObj.education:', copyObj.education);
console.log('obj.education === copyObj.education:', obj.education === copyObj.education);
console.log('----------拷贝值重新赋值----------');
copyObj.education.middle = '改后中学'
console.log('obj.education:', obj.education);
console.log('copyObj.education:', copyObj.education);
console.log('obj.education === copyObj.education:', obj.education === copyObj.education);
对应输出结果
其实根据===的恒等一直是true就知道,引用类型的属性值共同指向同一个内存地址,无论改变原始值还是拷贝值的属性,都会相互影响,是浅拷贝的状态。
综合看下来,我们不能直接说Object.assign()是浅拷贝或者深拷贝,
在对象属性仅是值类型的情况下,它可以实现深拷贝
在对象数据是引用类型的请跨下,它只是浅拷贝
不过大部分场景,我们用它来作为浅拷贝的代表,毕竟对象的属性很难确保一定只有值类型
3. 浅拷贝实现
Object.assign(target, ...sources)
let target = {};
let source = { a: { b: 2 } };
Object.assign(target, source);
target.a===source.a //true
4. 深拷贝实现
深拷贝方法其实有很多,最常用的最实用的就是这个了
4.1 实用简洁版
let co = JSON.parse(JSON.stringify(o));
直接类型强转,简单粗暴,其实我个人还挺喜欢(❤ ω ❤)
4.2 基础版
function deepCopy(obj) {
if(typeof obj === "object") {
if(obj.constructor === Array) {
var newArr = []
for(var i = 0; i < obj.length; i++) newArr.push(obj[i])
return newArr
} else {
var newObj = {}
for(var key in obj) {
newObj[key] = this.deepCopy(obj[key])
}
return newObj
}
} else {
return obj
}
}
这个也是经常使用的,通过类型判断进行逐层拷贝,也比较中规中矩,但是假如对象属性又指向对象本身造成循环引用的时候,该方法直接堆栈溢出报错了。
4.3 优化版
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function (obj, hash = new WeakMap()) {
if (hash.has(obj)) return hash.get(obj)
let type = [Date,RegExp,Set,Map,WeakMap,WeakSet]
if (type.includes(obj.constructor)) return new obj.constructor(obj);
//如果成环了,参数obj = obj.loop = 最初的obj 会在WeakMap中找到第一次放入的obj提前返回第一次放入WeakMap的cloneObj
let allDesc = Object.getOwnPropertyDescriptors(obj); //遍历传入参数所有键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc); //继承原型
hash.set(obj, cloneObj)
for (let key of Reflect.ownKeys(obj)) { //Reflect.ownKeys(obj)可以拷贝不可枚举属性和符号类型
// 如果值是引用类型(非函数)则递归调用deepClone
cloneObj[key] =
(isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ?
deepClone(obj[key], hash) : obj[key];
}
return cloneObj;
};
let obj = {
bigInt: BigInt(12312),
set:new Set([2]),
map:new Map([['a',22],['b',33]]),
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
obj: {
name: '我是一个对象',
id: 1
},
arr: [0, 1, 2],
func: function () {
console.log('我是一个函数')
},
date: new Date(0),
reg: new RegExp('/我是一个正则/ig'),
[Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', {
enumerable: false,
value: '不可枚举属性'
});
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj
let cloneObj = deepClone(obj);
console.log('obj', obj);
console.log('cloneObj', cloneObj);
for (let key of Object.keys(cloneObj)) {
if (typeof cloneObj[key] === 'object' || typeof cloneObj[key] === 'function') {
console.log(`${key}相同吗? `, cloneObj[key] === obj[key])
}
}
输出结果
这个版本忘记在哪里看的了,也不是我写的,比较牛的是,即考虑了循环引用,还有些特殊类型,用该方法近乎无敌,可以直接搬运当成工具类使用了,(如有侵权,请告知删除)