深拷贝是前端开发中经常用到的技术,其目的是为了克隆一个与原始对象完全独立的对象。说起来你可能不信,其实没有一个深拷贝可以复制所有数据类型,我们能做的, 就是实现大多数的类型,理解其原理, 了解其背后的知识网络,才是写他的意义。
数据类型
- 在 JavaScript 中,数据类型可以分为基本数据类型和引用数据类型。
基本数据类型:String, Number, Boolean, Null, Undefined
引用数据类型:Array, Object, Function ...
- 不同数据类型的判断
使用
typeof运算符来判断基本数据类型,使用Object.prototype.toString.call()来判断引用数据类型。
类型赋值
- 基本类型赋值
- 对象的属性访问和遍历方式(不可迭代类型赋值)
- 数组的遍历方式
- Set 和 Map 数据结构及其遍历方式
- 循环引用的处理方法
- 类型判断的方法
- 构造函数赋值方式(是否需要
new,new是否有参数) - TS类型控制
export enum CloneType {
Object = "Object",
Array = "Array",
Date = "Date",
RegExp = "RegExp",
Function = "Function",
String = "String",
Number = "Number",
Boolean = "Boolean",
Undefined = "Undefined",
Null = "Null",
Symbol = "Symbol",
Set = "Set",
Map = "Map"
}
export type _CloneType = keyof typeof CloneType
/**
* 检测数据类型
* @param type cloneType
* @param obj 检测的数据源
* @returns Boolean
*/
function isType<T>(type: _CloneType, obj: T) {
return Object.prototype.toString.call(obj) === `[object ${type}]`;
}
/**
* 深拷贝
* @param obj 要克隆的对象
* @param cache 缓存对象,用于解决循环引用的问题
* */
export function cloneDeep<T>(obj: T, cache = new WeakMap()): T {
// 如果不是对象或者是null,直接返回(终止条件)
if (typeof obj !== 'object' || obj === null) {
return obj
}
// 如果类型是Symbol,直接返回一个新的Symbol
if (isType(CloneType.Symbol, obj)) {
return obj.constructor((obj as unknown as Symbol).description)
}
// 如果已经缓存过,直接返回缓存的值
if (cache.has(obj)) {
return cache.get(obj)
}
// 初始化返回结果
let temp: T, param: T
// 如果是日期对象,直接返回一个新的日期对象
if (isType(CloneType.Date, obj) || isType(CloneType.RegExp, obj)) {
param = obj
}
// @ts-ignore
temp = new obj!.constructor(param)
// 如果是数组或者对象,需要遍历
if (isType(CloneType.Array, obj) || isType(CloneType.Object, obj)) {
Object.keys(obj)
.forEach(key => {
if (obj.hasOwnProperty(key)) {
temp[key] = cloneDeep(obj[key], cache)
}
})
}
// 如果是Set
if (isType(CloneType.Set, obj)) {
for (let value of (obj as unknown as Set<T>)) {
(temp as Set<T>).add(cloneDeep(value, cache))
}
}
// 如果是Map
if (isType(CloneType.Map, obj)) {
for (let [key, value] of (obj as unknown as Map<T, T>)) {
(temp as Map<T, T>).set(cloneDeep(key, cache), cloneDeep(value, cache))
}
}
// 缓存值
cache.set(obj, temp)
return temp
}
}