深拷贝
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 = acloneJSON(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 = oldObjoldObj.map = new Map([[1, oldObj]])oldObj.__proto__ = objvar 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方法源码
- 深拷贝的终极探索