const toString = Object.prototype.toString
const isEnumerable = Object.prototype.propertyIsEnumerable
function isFunction(data) {
return typeof data === 'function'
}
function isBigInt(data) {
return typeof data === 'bigint'
}
function isDate(data) {
return toString.call(data).includes('Date')
}
function isRegExp(data) {
return toString.call(data).includes('RegExp')
}
function isMap(data) {
return toString.call(data).includes('Map')
}
function isSet(data) {
return toString.call(data).includes('Set')
}
function cloneArray(data) {
return data.reduce((result, value) => {
return result.concat(cloneDeep(value))
}, [])
}
function getObjectKeys(data) {
const keys = []
for (const key in data) {
keys.push(key)
}
while (data) {
const symbols = Object.getOwnPropertySymbols(data).filter((s) =>
isEnumerable.call(data, s),
)
keys.push(...symbols)
data = Object.getPrototypeOf(data)
}
return keys
}
function cloneObject(data) {
const result = {}
const keys = getObjectKeys(data)
keys.forEach((key) => {
result[key] = cloneDeep(data[key])
})
return result
}
function cloneMap(data) {
const result = new Map()
data.forEach((val, key) => {
result.set(key, cloneDeep(val))
})
return result
}
function cloneSet(data) {
const set = new Set()
data.forEach((val) => {
set.add(cloneDeep(val))
})
return set
}
function saveAndReturn(stack, key, value) {
stack.set(key, value)
return value
}
export default function cloneDeep(data) {
const stack = cloneDeep.stack
if (stack.has(data)) {
return stack.get(data)
}
if (isFunction(data)) {
return {}
}
if (isBigInt(data)) {
return BigInt(data)
}
if (typeof data !== 'object' || data === null) {
return data
}
if (isDate(data)) {
return new Date(data.valueOf())
}
if (isRegExp(data)) {
const reg = new RegExp(data.source, data.flags)
reg.lastIndex = data.lastIndex
return reg
}
if (Array.isArray(data)) {
return saveAndReturn(stack, data, cloneArray(data))
}
if (isMap(data)) {
return saveAndReturn(stack, data, cloneMap(data))
}
if (isSet(data)) {
return saveAndReturn(stack, data, cloneSet(data))
}
return saveAndReturn(stack, data, cloneObject(data))
}
cloneDeep.stack = new Map()