快速实现
const myClone1 = (target) => {
return JSON.parse(JSON.stringify(target))
}
优点:简单明了,适合大部分场景
缺点:JSON.stringify方法的局限性,拷贝函数等类型有问题,可参考stringify的详解
基础版
浅拷贝
const myClone2 = (target) => {
const cloneTarget = {}
for (const key in target) {
cloneTarget[key] = target[key]
}
return cloneTarget
}
深拷贝
const myClone3 = (target) => {
if (typeof target === 'object') {
const cloneTarget = {}
for (const key in target) {
// 关键差异:递归实现深拷贝
cloneTarget[key] = myClone3(target[key])
}
return cloneTarget
} else {
return target
}
}
有了以上深拷贝的雏形,我们再继续探索更多特殊情况
考虑数组
const myClone4 = (target) => {
if (typeof target === 'object') {
// 关键差异:考虑数组情况
const cloneTarget = Array.isArray(target) ? [] : {}
for (const key in target) {
cloneTarget[key] = myClone4(target[key])
}
return cloneTarget
} else {
return target
}
}
以上我们的深拷贝方法已经可以处理多种情况,但是如果出现循环引用,递归进入死循环:
const x = {}
const y = {a: x}
x.y = y
myClone4(x) // Uncaught RangeError: Maximum call stack size exceeded
考虑循环引用
处理循环引用问题,我们只需要通过一个map来标记,如果存在对应target的key已经克隆过,就中断递归返回结果
const myClone5 = (target, map = new Map()) => {
if (typeof target === 'object') {
const cloneTarget = Array.isArray(target) ? [] : {}
// 关键差异:map标记key,如果有值则中断递归
if (map.get(target)) {
return map.get(target)
}
map.set(target, cloneTarget)
for (const key in target) {
cloneTarget[key] = myClone5(target[key], map)
}
return cloneTarget
} else {
return target
}
}
这里之所以用map而不用普通对象来缓存,主要考虑到键/值对中的键,这里是对象,使用map更方便
这里又有一个优化的点,这里的键只是用来做key的标记,可以使用WeakMap来替换Map
WeakMap对象中的键是弱引用,其键必须是对象,而值可以是任意的
弱引用:在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收
const myClone5 = (target, map = new WeakMap()) => { // 替换Map为WeakMap
if (typeof target === 'object') {
const cloneTarget = Array.isArray(target) ? [] : {}
if (map.get(target)) {
return map.get(target)
}
map.set(target, cloneTarget)
for (const key in target) {
cloneTarget[key] = myClone5(target[key], map)
}
return cloneTarget
} else {
return target
}
}
考虑其他类型
除了上面提到的普通对象和数组,还需要考虑Map、Set等情况,我们需要使用toString获取准确的引用类型
const G_TAG_TYPE = {
mapTag: '[object Map]',
weakMapTag: '[object WeakMap]',
setTag: '[object Set]',
arrayTag: '[object Array]',
objectTag: '[object Object]',
boolTag: '[object Boolean]',
dateTag: '[object Date]',
errorTag: '[object Error]',
numberTag: '[object Number]',
regexpTag: '[object RegExp]',
stringTag: '[object String]',
symbolTag: '[object Symbol]'
}
// 可以继续用myClone的类型
const G_DEEP_TAG = [G_TAG_TYPE.mapTag, G_TAG_TYPE.weakMapTag, G_TAG_TYPE.setTag, G_TAG_TYPE.arrayTag, G_TAG_TYPE.objectTag]
// Bool、Number、String、String、Date、Error、RegExp直接用构造函数和原始数据创建一个新对象:
const cloneOtherType = (target, type) => {
switch (type) {
case G_TAG_TYPE.boolTag:
case G_TAG_TYPE.numberTag:
case G_TAG_TYPE.stringTag:
case G_TAG_TYPE.errorTag:
case G_TAG_TYPE.dateTag:
return new target.constructor(target)
case G_TAG_TYPE.regexpTag: {
const reFlags = /\w*$/
const result = new target.constructor(target.source, reFlags.exec(target));
result.lastIndex = target.lastIndex;
return result
}
case G_TAG_TYPE.symbolTag:
return Object(Symbol.prototype.valueOf.call(target))
}
}
// null处理、函数兼容
const isObject = (target) => {
const type = typeof target
return target !== null && (type === 'object' || type === 'function')
}
const myClone6 = (target, map = new WeakMap()) => { // 替换Map为WeakMap
if (!isObject(target)) {
return target
}
const type = Object.prototype.toString.call(target)
let cloneTarget
if (G_DEEP_TAG.includes(type)) {
cloneTarget = new target.constructor()
} else {
return cloneOtherType(target, type)
}
if (map.get(target)) {
return map.get(target)
}
map.set(target, cloneTarget)
// 克隆set
if (type === G_TAG_TYPE.setTag) {
target.forEach(v => {
cloneTarget.add(myClone6(v, map))
})
return cloneTarget
}
// 克隆map和weakMap
if (type === G_TAG_TYPE.mapTag || type === G_TAG_TYPE.weakMapTag) {
target.forEach((v, key) => {
cloneTarget.set(key, myClone6(v, map))
})
return cloneTarget
}
for (const key in target) {
cloneTarget[key] = myClone6(target[key], map)
}
return cloneTarget
}
克隆函数
最后我们再考虑克隆函数
- 首先我们可以通过箭头函数没有prototype的特性,来区分箭头函数和普通函数
- 使用eval和函数字符串来重新生成一个箭头函数,注意这种方法是不适用于普通函数的
- 使用正则来处理普通函数,然后使用new Function构造函数重新构造一个新的函数
const cloneFunction = (func) => {
const bodyReg = /(?<={)(.|\n)+(?=})/m
const paramReg = /(?<=\().+(?=\)\s+{)/
const funcString = func.toString()
if (func.prototype) {
const param = paramReg.exec(funcString)
const body = bodyReg.exec(funcString)
if (body) {
if (param) {
const paramArr = param[0].split(',')
return new Function(...paramArr, body[0])
} else {
return new Function(body[0])
}
} else {
return null
}
} else {
return eval(funcString)
}
}
完整深拷贝函数
const G_TAG_TYPE = {
mapTag: '[object Map]',
weakMapTag: '[object WeakMap]',
setTag: '[object Set]',
arrayTag: '[object Array]',
objectTag: '[object Object]',
boolTag: '[object Boolean]',
dateTag: '[object Date]',
errorTag: '[object Error]',
numberTag: '[object Number]',
regexpTag: '[object RegExp]',
stringTag: '[object String]',
symbolTag: '[object Symbol]',
functionTag: '[object Function]'
}
// 可以继续用myClone的类型
const G_DEEP_TAG = [G_TAG_TYPE.mapTag, G_TAG_TYPE.weakMapTag, G_TAG_TYPE.setTag, G_TAG_TYPE.arrayTag, G_TAG_TYPE.objectTag]
// Bool、Number、String、String、Date、Error、RegExp直接用构造函数和原始数据创建一个新对象:
const cloneOtherType = (target, type) => {
switch (type) {
case G_TAG_TYPE.boolTag:
case G_TAG_TYPE.numberTag:
case G_TAG_TYPE.stringTag:
case G_TAG_TYPE.errorTag:
case G_TAG_TYPE.dateTag:
return new target.constructor(target)
case G_TAG_TYPE.regexpTag: {
const reFlags = /\w*$/
const result = new target.constructor(target.source, reFlags.exec(target));
result.lastIndex = target.lastIndex;
return result
}
case G_TAG_TYPE.symbolTag:
return Object(Symbol.prototype.valueOf.call(target))
case G_TAG_TYPE.functionTag:
return cloneFunction(target)
}
}
const cloneFunction = (func) => {
const bodyReg = /(?<={)(.|\n)+(?=})/m
const paramReg = /(?<=\().+(?=\)\s+{)/
const funcString = func.toString()
if (func.prototype) {
const param = paramReg.exec(funcString)
const body = bodyReg.exec(funcString)
if (body) {
if (param) {
const paramArr = param[0].split(',')
return new Function(...paramArr, body[0])
} else {
return new Function(body[0])
}
} else {
return null
}
} else {
return eval(funcString)
}
}
// null处理、函数兼容
const isObject = (target) => {
const type = typeof target
return target !== null && (type === 'object' || type === 'function')
}
const myClone7 = (target, map = new WeakMap()) => { // 替换Map为WeakMap
if (!isObject(target)) {
return target
}
const type = Object.prototype.toString.call(target)
let cloneTarget
if (G_DEEP_TAG.includes(type)) {
cloneTarget = new target.constructor()
} else {
return cloneOtherType(target, type)
}
if (map.get(target)) {
return map.get(target)
}
map.set(target, cloneTarget)
// 克隆set
if (type === G_TAG_TYPE.setTag) {
target.forEach(v => {
cloneTarget.add(myClone7(v, map))
})
return cloneTarget
}
// 克隆map和weakMap
if (type === G_TAG_TYPE.mapTag || type === G_TAG_TYPE.weakMapTag) {
target.forEach((v, key) => {
cloneTarget.set(key, myClone7(v, map))
})
return cloneTarget
}
for (const key in target) {
cloneTarget[key] = myClone7(target[key], map)
}
return cloneTarget
}