深拷贝
function createData(deep, breadth) {
var data = {}
var temp = data
for (let i = 0; i < deep; i++) {
temp = temp['data'] = {}
for (let j = 0; j < breadth; j++) {
temp[j] = j
}
}
return data
}
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
var isType = type => value => Object.prototype.toString.call(value) === `[object ${type}]`
var variableTypeDetection = {
isNumber: isType('Number'),
isString: isType('String'),
isBoolean: isType('Boolean'),
isNull: isType('Null'),
isUndefined: isType('Undefined'),
isSymbol: isType('Symbol'),
isFunction: isType('Function'),
isObject: isType('Object'),
isArray: isType('Array'),
isMap: isType('Map'),
isSet: isType('Set'),
isWeakMap: isType('WeakMap'),
isWeakSet: isType('WeakSet'),
isDate: isType('Date'),
isRegExp: isType('RegExp'),
isError: isType('Error')
}
variableTypeDetection.isNumber(1)
function cloneJSON(source) {
return JSON.parse(JSON.stringify(source))
}
// console.log(cloneJSON(createData(10000, 5)))
// cloneJSON 方法有循环引用检测
var a = {}
a.a = a
cloneJSON(a)
// 破解爆栈 - 递归改循环
function cloneLoop(source) {
var root = {}
var loopList = [{
parent: root,
key: undefined,
data: source
}]
// 循环
while(loopList.length) {
// 深度优先
var node = loopList.pop()
var parent = node.parent
var key = node.key
var data = node.data
// 初始化赋值目标,key 为 undefined 时,则拷贝到父元素,否则拷贝到子元素
let res = parent
if(typeof key !== 'undefined') {
res = parent[key] = {}
}
for(let key in data) {
if(data.hasOwnProperty(key)) {
if(typeof data[key] === 'object') {
// 下次循环
loopList.push({
parent: res,
key: key,
data: data[key]
})
} else {
res[key] = data[key]
}
}
}
}
return root
}
console.log(cloneLoop(createData(2, 3)))
function cloneDeep(obj, hash = new WeakMap()) {
if(!isObject(obj)) {
return obj
}
//
if(hash.has(obj)) {
return hash.get(obj)
}
let result = Array.isArray(obj) ? [] : {}
hash.set(obj, result)
Reflect.ownKeys(obj).forEach(item => {
if(isObject(obj[item])) {
result[item] = cloneDeep(obj[item], hash)
} else {
result[item] = obj[item]
}
})
return result
}
// 破解循环引用
function find(arr, item) {
for(let i = 0; i < arr.length; i++) {
if (arr[i].source === item) {
return arr[i]
}
}
return null
}
function cloneForce(source) {
// 用来去重
var uniqueList = []
var root = {}
var loopList = [{
parent: root,
key: undefined,
data: source
}]
// 循环
while(loopList.length) {
// 深度优先
var node = loopList.pop()
var parent = node.parent
var key = node.key
var data = node.data
// 初始化赋值目标,key 为 undefined 时,则拷贝到父元素,否则拷贝到子元素
let res = parent
if(typeof key !== 'undefined') {
res = parent[key] = {}
}
// 判断类型是否已存在
let uniqueData = find(uniqueList, data)
if(uniqueData) {
parent[key] = uniqueData.target
continue
}
// 若数据不存在,保存源数据,在拷贝数据中对应的引用
uniqueList.push({
source: data,
target: res
})
for(let key in data) {
if(data.hasOwnProperty(key)) {
if(typeof data[key] === 'object') {
// 下次循环
loopList.push({
parent: res,
key: key,
data: data[key]
})
} else {
res[key] = data[key]
}
}
}
}
return root
}
console.log(cloneLoop(createData(2, 3)))
function cloneDeep(obj, hash = new WeakMap()) {
if(hash.has(obj)) {
return obj
}
let result = null
var reference = [Date, RegExp, Set, Map, WeakSet, WeakMap, Error]
if(reference.includes(obj?.constructor)) {
// 学习方法
result = new obj.constructor(obj)
} else if (Array.isArray(obj)) {
result = []
obj.forEach((item, index) => {
result[index] = cloneDeep(item)
})
} else if (isObject(obj)) {
hash.set(obj)
result = {}
for(let key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
result[key] = cloneDeep(obj[key], hash)
}
}
} else {
result = obj
}
return result
}
// 获取类型
var Type = param => Object.prototype.toString.call(param)
//获取正则标识
var getRegFlags = reg => (reg + "").replace(/\/(.*)\//, "")
var clone = param => {
//声明父子数组解决循环引用
var parent = []
var children = []
//判断条件过多,声明map数据结构.也可以使用对象
var map = new Map()
var _clone = param => {
var child, proto
//如果为值类型数据
if ((typeof param !== 'object' && typeof param !== 'function') || param === null) {
//如果为symbol获取symbol的值,然后重新建一个symbol(我查阅资料没找到获取symbol描述的api,因此用正则获取)
child = typeof param === 'symbol' ? (/\((.*)\)/.test(param.toString())) && Symbol(RegExp.$1) : param
}
//如果为引用数据类型
else {
//对函数进行克隆
map.set('[object Function]', () => {
//获取函数字符串并使用eval函数将字符串当做js代码执行
var fn = param.toString()
child = eval(`(${fn})`)
})
//对数组进行克隆
map.set('[object Array]', () => {
child = []
})
//对对象进行克隆
map.set('[object Object]', () => {
//为孩子重新构造原型关系
proto = Object.getPrototypeOf(param)
child = Object.create(proto)
})
//对正则进行克隆
map.set('[object RegExp]', () => {
child = new RegExp(param.source, getRegFlags(param))
})
//对日期进行克隆
map.set('[object Date]', () => {
// Date.parse(dateString) 解析一个日期字符串,返回值是时间戳(毫秒数),等价于:new Date().getTime()
child = new Date(Date.parse(param))
})
//对map进行克隆
map.set('[object Map]', () => {
// child = new Map([...Array.from(param)])
child = new Map()
})
//对set进行克隆
map.set('[object Set]', () => {
// child = new Set([...Array.from(param)])
child = new Set()
})
}
//如果是引用数据类型(map只加了引用数据类型) 执行函数
map.has(Type(param)) && map.get(Type(param))()
// 处理循环引用
var index = parent.indexOf(param)
// 如果前面已经引用过,直接返回此对象如下例中,引用过两次oldObj
if (index !== -1) {
console.log('有循环引用...', children[index])
return children[index]
}
parent.push(param)
children.push(child)
// 如果param对象
if (param && typeof param === 'object') {
// 如果为set
if (Type(param) === '[object Set]') {
for (let item of param.keys()) {
child.add(_clone(item))
}
}
// 如果为map
else if (Type(param) === '[object Map]') {
for (let item of param.keys()) {
child.set(item, _clone(param.get(item)))
}
}
else {
Reflect.ownKeys(param).forEach(item => {
//递归调用
child[item] = _clone(param[item])
})
}
}
return child
}
return _clone(param)
}
// 测试各类数据
function person(name) {
this.name = name;
}
var HuangLiHao = new person('HuangLiHao');
function fn() {
console.log('3');
}
var xx = Symbol('xx')
var obj = { y: 3 }
var oldObj = {
a: fn,
zz: fn,
c: new RegExp('ab+c', 'gi'),
d: HuangLiHao,
x: new Date(),
mum: 10,
bool: true,
str: '3',
null: null,
und: undefined,
sym: Symbol(123),
[xx]: 5,
array: [1, 3, 4, [2, 4]],
set: new Set([2, 3, 43, 6546, '2'])
}
oldObj.b = oldObj
oldObj.map = new Map([[1, oldObj]])
oldObj.__proto__ = obj
var newObj = clone(oldObj)
console.log(newObj);
// console.log(newObj.y);
// var map = new Set()
const isType = (type: string) => (value:any) => Object.prototype.toString.call(value) === `[object ${type}]`
const validateType = {
isDate: isType('Date'),
isMap: isType('Map'),
isSet: isType('Set'),
isArray: isType('Array')
}
export function cloneDeep(obj: any, map = new WeakMap()): any {
if (typeof obj !== 'object' || obj === null) return obj
// 避免循环引用
if (map.has(obj)) {
return map.get(obj)
}
let target: any = {}
map.set(obj, target)
// Set
if (validateType.isSet(obj)) {
target = new Set()
obj.forEach((val: any) => {
const newVal = cloneDeep(val, map)
target.add(newVal)
});
}
// Map
if (validateType.isMap(obj)) {
target = new Map()
obj.forEach((v: any, k: any) => {
const newKey = cloneDeep(k, map)
const newVal = cloneDeep(v, map)
target.set(newKey, newVal)
});
}
// Date
if (validateType.isDate(obj)) {
target = new Date(Date.parse(obj))
}
// Array
if (validateType.isArray(obj)) {
target = obj.map((item: any) => cloneDeep(item, map))
}
// Object
for (const key in obj) {
const val = obj[key]
const val1 = cloneDeep(val, map)
target[key] = val1
}
return target
}
参考文档
- 深拷贝的终极探索、你最少用几行代码实现深拷贝?
- jQuery中的extend方法源码
- 深拷贝的终极探索