本文主要说明如何实现深拷贝和浅拷贝。
浅拷贝是创建一个新对象,这个新对象有着原始对象属性值的一份拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址。
// 浅拷贝
function shallowClone(obj) {
if(obj === undefined) return undefined
if(obj === null) return null
// 数据是基本型数据,直接返回
if(typeof obj !== 'object') return obj
// 对象为空,直接返回
if(!Object.keys(obj).length) return obj
// 据obj的类型判断是新建数组还是对象
let newClone = Array.isArray(obj) ? [] : {}
for(let key in obj) {
//判断当前对象/数组是否有自身的属性 不包括继承
// 核心
if(obj.hasOwnProperty(key)) {
// 赋值给新对象/数组
newClone[key] = obj[key]
}
}
return newClone
}
深拷贝是创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
// 深拷贝:采用循环递归方式
function deepClone(obj, wm = new WeakMap()) {
// 对数据预处理
if(obj === undefined) return undefined
if(obj === null) return null
if(obj instanceof Date) return new Date(obj)
if(obj instanceof RegExp) return new RegExp(obj)
if(obj instanceof Error) return new Error(obj.message)
// 中断条件
// 若数据是基本类型,则直接返回不拷贝
// 对象为空,则直接返回不拷贝
if(typeof obj !== 'object') return obj
if(!Object.keys(obj).length) return obj
// 若WeakMap已存在指定键的元素,则直接存储返回元素,不必重新建立若引用,节约空间
if(wm.has(obj)) return wm.get(obj)
// obj若为实例对象,使用其构造函数创建新实例对象,新对象包含于obj相同的属性
// obj若为普通对象,new obj.constructor()等价于new Object()
// 为什么?执行 obj = { age: 19 };console.log(obj.constructor == Object) 一下就知道了
let newClone = new obj.constructor()
wm.set(obj, newClone)
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
// 核心
newClone[key] = deepClone(obj[key], wm)
}
}
return newClone
}
为什么使用 WeakMap ?
主要为了解决 循环引用 的问题。循环引用(circular references)是指在对象之间存在相互引用的情况,形成一个闭环,导致对象无法被完全释放和垃圾回收。循环引用发生在当一个对象的属性或成员引用另一个对象,并且这个被引用的对象又直接或间接地引用回原始对象,从而形成一个循环。
当存在循环引用时,JavaScript的垃圾回收机制可能无法正确地处理这些对象,因为它们之间的引用形成了一个无法访问的闭环,无法确定哪些对象是不再被使用的。这可能导致内存泄漏,即占用的内存无法被回收,最终导致内存资源的浪费和性能问题。
// 1、浅拷贝
console.log('浅拷贝')
const str1 = '123'
const str2 = shallowClone(str1)
console.log('str2 : ', str2)
console.log('str1 === str2 : ', str1 === str2)
console.log('-------------------------')
const obj1 = {
name: 'init',
arr: [1, [2, 3], 4],
}
const obj2 = shallowClone(obj1)
console.log('obj1 === obj2 : ', obj1 === obj2, '\nobj2 = ', obj2)
console.log('-------------------------')
obj2.name = 'new'
obj2.arr[1] = [4, 5, 6]
console.log('obj1 : ', obj1)
// 属性的引用类型,新旧对象还是共享同一块内存
console.log('obj2 : ', obj2)
console.log('----------------------------------------------------------')
// 2、深拷贝
console.log('深拷贝')
let deep = {
name: 'shuaige',
age: 12,
boo: true,
n: null,
un: undefined,
sy: Symbol('xx'),
big: 10n,
child: {
ele: 'boby',
x: 100
},
arr: [1, 2, 3],
reg: /^\d+$/,
fn: function () {
console.log(this.name);
},
time: new Date(),
}
const newDeep = deepClone(deep)
// 循环使用
newDeep.loop = newDeep
console.log(newDeep)