前言
在前端中对象深拷贝是比较常见的面试题,在日常开发使用中也很常见,很多人在使用的时候也许会有一些类似于JSON.stringify
这种技巧来实现深度克隆,但是在写一些中大型的项目中都会实现deepClone
的工具方法, 今天介绍一下我在项目中如何实现以及如何去标注deepClone
函数类型的
deepClone
实现思路
- 判断传参值类型,当为
null
或者不为object
时, 直接返回传参 - 为了避免对象的循环引用,一般使用 WeakMap 来储存每次循环时的健值,同时 WeakMap 也不会影响JS的垃圾回收。
- 每次判断一下传入的
obj
是否在cache
中已经存在,如果存在就将值直接返回 - 此时已经确认obj是一个对象,继续确认它的类型,是否为数组
- 到这一步已经确认了传入的
obj
是对象且并未被缓存过,缓存该对象 - 递归调用自身,直到整个对象被拷贝完成
实现JS代码
先上一个JS版本的代码,关于如何标注类型后续完成
scss
function deepClone(obj) {
var cache = new WeakMap();
function _deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (cache.has(obj)) {
return cache.get(obj);
}
var result = Array.isArray(obj)
? []
: {};
cache.set(obj, result);
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = _deepClone(obj[key]);
}
}
return result;
}
return _deepClone(obj);
}
TS类型标注
看到很多人在TS中就参数使用一个any
作为标注,这样的话在调用时以及调用之后类型不明确,既然是一个对象深度克隆函数, 那么返回的肯定是和传入参数是同一个类型,这里可以使用一个泛型U来制定类型, 返回值也为该类型,在内部函数泛型T的循环中,result每次保存的类型都是泛型T的某个属性或者对象, 所以result可以看作是Partial<T>
, 在本次对象的所有循环赋值结束后 result 的类型才变为 T, 所以可以使用as
断言result的类型
vbnet
function deepClone<U>(obj: U): U {
const cache = new WeakMap()
function _deepClone<T>(obj: T): T {
if (obj === null || typeof obj !== 'object') {
return obj
}
if (cache.has(obj)) {
return cache.get(obj)
}
const result = Array.isArray(obj)
? ([] as unknown as Partial<T>)
: ({} as Partial<T>)
cache.set(obj, result)
for (const key in obj) {
if ((obj as Object).hasOwnProperty(key)) {
result[key as keyof T] = _deepClone(obj[key])
}
}
return result as T
}
return _deepClone(obj)
}
这样的类型就没问题了。使用函数后赋值类型也能明确了。