Skip to content

Commit

Permalink
feat: support clone
Browse files Browse the repository at this point in the history
  • Loading branch information
haoziqaq committed Oct 31, 2024
1 parent c428866 commit 9e42734
Show file tree
Hide file tree
Showing 7 changed files with 487 additions and 46 deletions.
2 changes: 1 addition & 1 deletion docs/function/debounce.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ window.addEventListener('resize', debouncedFn)
| Arg | Type | Defaults |
| ------- | :--------: | -------: |
| `fn` | `Function` | |
| `delay` | `number` | 0 |
| `delay` | `number` | `0` |

### Return

Expand Down
2 changes: 1 addition & 1 deletion docs/zh/function/debounce.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ window.addEventListener('resize', debouncedFn)
| 参数 | 类型 | 默认值 |
| ------- | :--------: | -----: |
| `fn` | `Function` | |
| `delay` | `number` | 0 |
| `delay` | `number` | `0` |

### 返回值

Expand Down
125 changes: 116 additions & 9 deletions src/collection.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { hasOwn, isArray, isObject } from './general'
import { isArrayBuffer, isDataView, isRegExp, isTypedArray, isWeakMap } from 'util/types'
import { hasOwn, isArray, isDate, isMap, isObject, isPlainObject, isSet, isWeakSet, toRawType } from './general'

export function mergeWith<TObject extends Record<string, any>, TSource extends Record<string, any>>(
object: TObject,
source: TSource,
callback: (
objValue: any,
srcValue: any,
key: string | number | symbol,
object?: TObject,
source?: TSource,
) => any | void,
fn: (objValue: any, srcValue: any, key: string | number | symbol, object?: TObject, source?: TSource) => any,
): TObject & TSource {
function baseMerge(target: any, src: any): any {
// eslint-disable-next-line no-restricted-syntax
Expand All @@ -18,7 +13,7 @@ export function mergeWith<TObject extends Record<string, any>, TSource extends R
const srcValue = src[key]
const targetValue = target[key]

const customResult = callback(targetValue, srcValue, key, object, source)
const customResult = fn(targetValue, srcValue, key, object, source)

if (customResult !== undefined) {
target[key] = customResult
Expand All @@ -45,3 +40,115 @@ export function merge<TObject extends Record<string, any>, TSource extends Recor
) {
return mergeWith(object, source, () => undefined)
}

export function cloneDeep<T>(value: T): T {
return cloneDeepWith(value, () => undefined)
}

export function cloneDeepWith<T>(value: T, fn: (value: any) => any): T {
const cache = new WeakMap()

function baseCloneDeep(value: any, cache: WeakMap<any, any>): any {
const customResult = fn(value)

if (customResult !== undefined) {
return customResult
}

if (!isObject(value)) {
return value
}

if (cache.has(value)) {
return cache.get(value)
}

if (isDate(value)) {
return new Date(value)
}

if (isRegExp(value)) {
return new RegExp(value)
}

if (isMap(value)) {
const result = new Map()
cache.set(value, result)

value.forEach((val: any, key: any) => {
result.set(baseCloneDeep(key, cache), baseCloneDeep(val, cache))
})

return result
}

if (isSet(value)) {
const result = new Set()
cache.set(value, result)

value.forEach((val: any) => {
result.add(baseCloneDeep(val, cache))
})

return result
}

if (toRawType(value) === 'String' || toRawType(value) === 'Number' || toRawType(value) === 'Boolean') {
return newConstructor(value, value.valueOf())
}

if (isWeakMap(value) || isWeakSet(value)) {
return {}
}

if (isTypedArray(value)) {
return newConstructor(value, baseCloneArrayBuffer(value.buffer), value.byteOffset, value.length)
}

if (isDataView(value)) {
return newConstructor(value, baseCloneArrayBuffer(value.buffer), value.byteOffset, value.byteLength)
}

if (isArrayBuffer(value)) {
return baseCloneArrayBuffer(value)
}

if (isArray(value)) {
const result: any[] = []
cache.set(value, result)

value.forEach((value, index) => {
result[index] = baseCloneDeep(value, cache)
})

return result
}

if (isPlainObject(value)) {
const result: Record<string, any> = Object.create(Reflect.getPrototypeOf(value))
cache.set(value, result)

for (const key in value) {

Check failure on line 131 in src/collection.ts

View workflow job for this annotation

GitHub Actions / test

for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array
if (hasOwn(value, key)) {
result[key] = baseCloneDeep(value[key], cache)
}
}

return result
}

return value
}

function baseCloneArrayBuffer(value: ArrayBuffer): ArrayBuffer {
const result = new ArrayBuffer(value.byteLength)
new Uint8Array(result).set(new Uint8Array(value))
return result
}

function newConstructor(value: any, ...args: any[]) {
return new value.constructor(...args)
}

return baseCloneDeep(value, cache)
}
40 changes: 36 additions & 4 deletions src/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,51 @@ export function isPromise<T = any>(val: unknown): val is Promise<T> {
}

export function isMap(val: unknown): val is Map<any, any> {
return toTypeString(val) === '[object Map]'
return toRawType(val) === 'Map'
}

export function isSet(val: unknown): val is Set<any> {
return toTypeString(val) === '[object Set]'
return toRawType(val) === 'Set'
}

export function isDate(val: unknown): val is Date {
return toTypeString(val) === '[object Date]'
return toRawType(val) === 'Date'
}

export function isRegExp(val: unknown): val is RegExp {
return toTypeString(val) === '[object RegExp]'
return toRawType(val) === 'RegExp'
}

export function isWeakMap(val: unknown): val is WeakMap<any, any> {
return toRawType(val) === 'WeakMap'
}

export function isWeakSet(val: unknown): val is WeakSet<any> {
return toRawType(val) === 'WeakSet'
}

export function isArrayBuffer(val: unknown): val is ArrayBuffer {
return toRawType(val) === 'ArrayBuffer'
}

export function isTypedArray(val: unknown) {
return [
'Int8Array',
'Uint8Array',
'Uint8ClampedArray',
'Int16Array',
'Uint16Array',
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array',
'BigInt64Array',
'BigUint64Array',
].includes(toRawType(val))
}

export function isDataView(val: unknown): val is DataView {
return toRawType(val) === 'DataView'
}

export function toRawType(value: unknown): string {
Expand Down
Loading

0 comments on commit 9e42734

Please sign in to comment.