JS中拷贝对象可以按照拷贝的程度可以分为浅拷贝和深拷贝,有些时候我们需要拷贝之后的对象和拷贝之前的对象解耦,即脱离联系,也就是改变其中一者,另一者不会变化,典型的场景有:状态的回溯。如果我们对状态对象使用浅拷贝,则无法对状态进行回溯,但如果使用深拷贝,则可以很容易的对状态进行回溯和跟踪。
实现深拷贝,主要有以下两种方式:(值得一提的是,JS原生数组中的 concat、slice 方法还有 Object.assign 方法都是一层拷贝,即浅拷贝)
1、JSON 法
function copy(o) {
return JSON.parse(JSON.stringify(o))
}
注:该方法无法拷贝函数,如果对象中有值是函数,函数被不会正确拷贝。
2、递归实现
var deepCopy = function(obj) {
if (typeof obj !== 'object') return obj
var newObj = (Object.prototype.toString.call(obj) === '[object Array]') ? [] : {}
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = (typeof obj[key] !== 'object') ? obj[key]: deepCopy(obj[key])
}
}
return newObj
}
上面的方法只能实现{} []或者子元素是{} []的深拷贝,如果有一下要求怎么实现
要求:
支持对象、数组、日期、正则的拷贝。
处理原始类型(原始类型直接返回,只有引用类型才有深拷贝这个概念)。
处理 Symbol 作为键名的情况。
处理函数(函数直接返回,拷贝函数没有意义,两个对象使用内存中同一个地址的函数,问题不大)。
处理 DOM 元素(DOM 元素直接返回,拷贝 DOM 元素没有意义,都是指向页面中同一个)。
额外开辟一个储存空间 WeakMap,解决循环引用递归爆栈问题(引入 WeakMap 的另一个意义,配合垃圾回收机制,防止内存泄漏)。
答案:
function deepClone (target, hash = new WeakMap()) { // 额外开辟一个存储空间WeakMap来存储当前对象
if (target === null) return target // 如果是 null 就不进行拷贝操作
if (target instanceof Date) return new Date(target) // 处理日期
if (target instanceof RegExp) return new RegExp(target) // 处理正则
if (target instanceof HTMLElement) return target // 处理 DOM元素
if (typeof target !== 'object') return target // 处理原始类型和函数 不需要深拷贝,直接返回
// 是引用类型的话就要进行深拷贝
if (hash.get(target)) return hash.get(target) // 当需要拷贝当前对象时,先去存储空间中找,如果有的话直接返回
const cloneTarget = new target.constructor() // 创建一个新的克隆对象或克隆数组
hash.set(target, cloneTarget) // 如果存储空间中没有就存进 hash 里
Reflect.ownKeys(target).forEach(key => { // 引入 Reflect.ownKeys,处理 Symbol 作为键名的情况
cloneTarget[key] = deepClone(target[key], hash) // 递归拷贝每一层
})
return cloneTarget // 返回克隆的对象
}
测试一下:
const obj = {
a: true,
b: 100,
c: 'str',
d: undefined,
e: null,
f: Symbol('f'),
g: {
g1: {} // 深层对象
},
h: [], // 数组
i: new Date(), // Date
j: /abc/, // 正则
k: function () {}, // 函数
l: [document.getElementById('foo')] // 引入 WeakMap 的意义,处理可能被清除的 DOM 元素
}
obj.obj = obj // 循环引用
const name = Symbol('name')
obj[name] = 'lin' // Symbol 作为键
const newObj = deepClone(obj)
console.log(newObj)
参考:
轻松拿下 JS 浅拷贝、深拷贝