const MAP_TAG = '[object Map]'
const SET_TAG = '[object Set]'
const ARRAY_TAG = '[object Array]'
const OBJECT_TAG = '[object Object]'
const BOOLEAN_TAG = '[object Boolean]'
const DATE_TAG = '[object Date]'
const ERROR_TAG = '[object Error]'
const NUMBER_TAG = '[object Number]'
const REGEXP_TAG = '[object RegExp]'
const STRING_TAG = '[object String]'
const SYMBOL_TAG = '[object Symbol]'
const FUNCTION_TAG = '[object Function]'
const CAN_TARVERSE_TAGS = [MAP_TAG, SET_TAG, ARRAY_TAG, OBJECT_TAG]
const CANT_CLONE_TAGS = [ERROR_TAG, FUNCTION_TAG]
function isReference(target) {
return (
target !== null &&
(typeof target === 'object' || typeof target === 'function')
)
}
function getType(target) {
return Object.prototype.toString.call(target)
}
function getInitialReference(target) {
return new target.constructor()
}
function cloneCantTarverseReference(target, type) {
function cloneRegExp(regexp) {
const reFlags = /\w*$/
const result = new regexp.constructor(regexp.source, reFlags.exec(regexp))
result.lastIndex = regexp.lastIndex
return result
}
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe))
}
if (CANT_CLONE_TAGS.includes(type)) {
return target
}
if ([BOOLEAN_TAG, NUMBER_TAG, STRING_TAG, DATE_TAG].includes(type)) {
return new target.constructor(target)
} else if (type === REGEXP_TAG) {
return cloneRegExp(target)
} else if (type === SYMBOL_TAG) {
return cloneSymbol(target)
} else {
return null
}
}
function cloneCanTarverseReference(originTarget, cloneTarget, type, cycleMap) {
function cloneMap() {
for (const [key, value] of originTarget) {
cloneTarget.set(key, cloneDeep(value, cycleMap))
}
}
function cloneSet() {
for (const value of originTarget) {
cloneTarget.add(cloneDeep(value, cycleMap))
}
}
function cloneArray() {
for (const value of originTarget) {
cloneTarget.push(cloneDeep(value, cycleMap))
}
}
function cloneObject() {
for (const [key, value] of Object.entries(originTarget)) {
cloneTarget[key] = cloneDeep(value, cycleMap)
}
}
if (type === MAP_TAG) {
cloneMap()
} else if (type === SET_TAG) {
cloneSet()
} else if (type === ARRAY_TAG) {
cloneArray()
} else if (type === OBJECT_TAG) {
cloneObject()
}
}
function cloneDeep(target, cycleMap = new Map()) {
if (!isReference(target)) return target
const referenceType = getType(target)
if (!CAN_TARVERSE_TAGS.includes(referenceType)) {
return cloneCantTarverseReference(target, referenceType)
}
const pureReference = getInitialReference(target)
if (cycleMap.has(target)) {
return cycleMap.get(target)
} else {
cycleMap.set(target, pureReference)
}
cloneCanTarverseReference(target, pureReference, referenceType, cycleMap)
return pureReference
}