const deepClasses = ['Map', 'Set', 'Array', 'Object', 'Arguments']
function classof(o) {
return Object.prototype.toString.call(o).slice(8, -1)
}
function isObject(target) {
const type = typeof target
return target !== null && (type === 'object' || type === 'function')
}
function getValue(classConstructor, o) {
return classConstructor.prototype.valueOf.call(o)
}
function getInit(target) {
const Ctor = target.constructor
return new Ctor()
}
function cloneFunction(source) {
if (source.prototype) {
return function() {
return source.apply(this, arguments)
}
} else {
return eval(source.toString())
}
}
function cloneOtherType(source, className) {
const Ctor = source.constructor
switch (className) {
case 'Boolean':
case 'Number':
case 'String':
case 'Error':
case 'Date':
return new Ctor(getValue(Ctor, source))
case 'RegExp':
return new Ctor(source.source, source.flags)
case 'Symbol':
return Object(getValue(Ctor, source))
case 'Function':
return cloneFunction(source)
default:
return new Ctor
}
}
function cloneDeep(source, allObj = new WeakMap) {
let target = null
if (isObject(source)) {
const className = classof(source)
if (deepClasses.includes(className)) {
target = getInit(source)
} else {
target = cloneOtherType(source, className)
}
if (allObj.has(source)) {
return allObj.get(source)
} else {
allObj.set(source, target)
}
if (className === 'Map') {
source.forEach((value, key) => {
target.set(key, cloneDeep(value, allObj))
})
} else if (className === 'Set') {
source.forEach(value => {
target.add(cloneDeep(value, allObj))
})
}
const keys = Reflect.ownKeys(source)
keys.forEach(key => {
const value = source[key]
Object.defineProperty(target, key, {value: cloneDeep(value, allObj)})
})
} else {
target = source
}
return target
}