Skip to content

Commit

Permalink
fix(utils): deepCopy -> cloneDeep 네이밍 변경 및 기능 개선
Browse files Browse the repository at this point in the history
  • Loading branch information
ssi02014 committed Oct 16, 2024
1 parent 2adc3d3 commit a267fda
Show file tree
Hide file tree
Showing 12 changed files with 109 additions and 73 deletions.
5 changes: 5 additions & 0 deletions .changeset/tasty-crabs-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modern-kit/utils': minor
---

fix(utils): deepCopy -> cloneDeep 네이밍 변경 및 기능 개선 - @ssi02014
48 changes: 48 additions & 0 deletions docs/docs/utils/common/cloneDeep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# cloneDeep

인자로 주어진 값을 `깊은 복사`를 수행하는 함수입니다.

<br />

## Code
[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/utils/src/common/cloneDeep/index.ts)

## Benchmark
- `hz`: 초당 작업 수
- `mean`: 평균 응답 시간(ms)

|이름|hz|mean|성능|
|------|---|---|---|
|modern-kit/cloneDeep|1,529,157.20|0.0007|`fastest`|
|lodash/cloneDeep|650,320.39|0.0015|-|

- **modern-kit/cloneDeep**
- `2.35x` faster than lodash/cloneDeep

## Interface
```ts title="typescript"
function cloneDeep<T>(value: T): T
```

## Usage
```ts title="typescript"
import { cloneDeep } from '@modern-kit/utils';
const originNum = 42;
const copyNum = cloneDeep(originNum);
const originObj = { a: 1, b: { c: 2 } };
const copyObj = cloneDeep(originObj);
const originArray = [1, 2, [3, 4]];
const copyArray = cloneDeep(originArray);
const originSet = new Set([1, 2, 3]);
const copySet = cloneDeep(originSet);
const originMap = new Map([
['a', 1],
['b', 2],
]);
const copyMap = cloneDeep(originMap);
```
36 changes: 0 additions & 36 deletions docs/docs/utils/common/deepCopy.md

This file was deleted.

6 changes: 3 additions & 3 deletions docs/docs/utils/object/omit.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@

|이름|hz|mean|성능|
|------|---|---|---|
|modern-kit/omit|3,567,312.89|0.0003|`fastest`|
|lodash/omit|879,967.75|0.0011|-|
|modern-kit/omit|1,505,400.16|0.0003|`fastest`|
|lodash/omit|901,269.43|0.0011|-|

- **modern-kit/omit**
- `4.05x` faster than lodash/omit
- `1.67x` faster than lodash/omit

## Interface
```ts title="typescript"
Expand Down
8 changes: 3 additions & 5 deletions docs/docs/utils/object/pick.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

인자로 넣은 `keys`로 구성된 객체를 반환하는 함수입니다. 반환된 객체는 `깊은 복사된 새로운 객체`입니다.

`symbol`은 제외됩니다.

<br />

## Code
Expand All @@ -15,11 +13,11 @@

|이름|hz|mean|성능|
|------|---|---|---|
|modern-kit/pick|5,663,602.36|0.0002|`fastest`|
|lodash/pick|1,035,576.42|0.0010|-|
|modern-kit/pick|1,693,028.73|0.0002|`fastest`|
|lodash/pick|1,022,887.39|0.0010|-|

- **modern-kit/pick**
- `4.05x` faster than lodash/pick
- `1.60x` faster than lodash/pick

## Interface
```ts title="typescript"
Expand Down
13 changes: 13 additions & 0 deletions packages/utils/src/common/cloneDeep/cloneDeep.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { bench, describe } from 'vitest';
import { cloneDeep as cloneDeep } from '.';
import { cloneDeep as cloneDeepLodash } from 'lodash-es';

describe('cloneDeep', () => {
bench('@modern-kit/cloneDeep', () => {
cloneDeep({ a: 1, b: 2, c: { d: 4, e: [1, 2, 3] } });
});

bench('lodash/cloneDeep', () => {
cloneDeepLodash({ a: 1, b: 2, c: { d: 4, e: [1, 2, 3] } });
});
});
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { describe, it, expect } from 'vitest';
import { deepCopy } from '.';
import { cloneDeep } from '.';

describe('deepCopy', () => {
describe('cloneDeep', () => {
it('should deeply copy a primitive value', () => {
const originNum = 42;
const copiedNum = deepCopy(originNum);
const copiedNum = cloneDeep(originNum);

expect(copiedNum).toBe(originNum);
});

it('should deeply copy an array', () => {
const originArray = [1, 2, [3, 4]];
const copiedArray = deepCopy(originArray);
const copiedArray = cloneDeep(originArray);

expect(copiedArray).toEqual(originArray);
expect(copiedArray).not.toBe(originArray);
Expand All @@ -21,7 +21,7 @@ describe('deepCopy', () => {
const originArray: any[] = [];
originArray.push(originArray);

const copiedArr = deepCopy(originArray);
const copiedArr = cloneDeep(originArray);

expect(copiedArr).toEqual(copiedArr[0]);
expect(copiedArr).not.toBe(originArray);
Expand All @@ -32,24 +32,25 @@ describe('deepCopy', () => {
const originObject = { origin: {} };
originObject.origin = originObject;

const copiedObject = deepCopy(originObject);
const copiedObject = cloneDeep(originObject);

expect(copiedObject).toEqual(copiedObject.origin);
expect(copiedObject).not.toBe(originObject);
expect(copiedObject).not.toBe(originObject.origin);
});

it('should deeply copy an object', () => {
const originObj = { a: 1, b: { c: 2 } };
const copiedObj = deepCopy(originObj);
const originObj = { a: 1, b: { c: 2, d: [0, 1] } };
const copiedObj = cloneDeep(originObj);

expect(copiedObj).toEqual(originObj);
expect(copiedObj).not.toBe(originObj);
expect(copiedObj.b.d).not.toBe(originObj.b.d);
});

it('should deeply copy a set', () => {
const originSet = new Set([1, 2, 3]);
const copiedSet = deepCopy(originSet);
const copiedSet = cloneDeep(originSet);

expect(copiedSet).toEqual(originSet);
expect(copiedSet).not.toBe(originSet);
Expand All @@ -60,23 +61,23 @@ describe('deepCopy', () => {
['a', 1],
['b', 2],
]);
const copiedMap = deepCopy(originMap);
const copiedMap = cloneDeep(originMap);

expect(copiedMap).toEqual(originMap);
expect(copiedMap).not.toBe(originMap);
});

it('should deeply copy a date', () => {
const date = new Date();
const copiedDate = deepCopy(date);
const copiedDate = cloneDeep(date);

expect(copiedDate.getTime()).toEqual(date.getTime());
expect(copiedDate).not.toBe(date);
});

it('should deeply copy a regex', () => {
const regex = /test/gi;
const copiedRegex = deepCopy(regex);
const copiedRegex = cloneDeep(regex);

expect(copiedRegex.source).toEqual(regex.source);
expect(copiedRegex.flags).toEqual(regex.flags);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function deepCopy<T>(value: T) {
export function cloneDeep<T>(value: T) {
const referenceMap = new WeakMap();

const copyWthRecursion = (target: T): T => {
Expand All @@ -14,20 +14,20 @@ export function deepCopy<T>(value: T) {

// Array
if (Array.isArray(target)) {
const newArray: any[] = [];

const newArray = new Array(target.length);
referenceMap.set(target, newArray);
for (const item of target) {
newArray.push(copyWthRecursion(item));

for (let i = 0; i < target.length; i++) {
newArray[i] = copyWthRecursion(target[i]);
}
return newArray as T;
}

// Set
if (target instanceof Set) {
const newSet = new Set();

referenceMap.set(target, newSet);

for (const item of target) {
newSet.add(copyWthRecursion(item));
}
Expand All @@ -52,18 +52,21 @@ export function deepCopy<T>(value: T) {

// RegExp
if (target instanceof RegExp) {
return new RegExp(target.source, target.flags) as T;
const result = new RegExp(target.source, target.flags);
result.lastIndex = target.lastIndex;

return result as T;
}

// Object
const newObject: Record<PropertyKey, any> = Object.create(
Object.getPrototypeOf(target)
);

const newObject: Record<PropertyKey, any> = {};
referenceMap.set(target, newObject);

const keys = Reflect.ownKeys(target); // symbol 유지

for (const key of keys) {
for (let i = 0; i < keys.length; i++) {
const key = keys[i];

newObject[key] = copyWthRecursion(
(target as Record<PropertyKey, any>)[key]
);
Expand Down
2 changes: 1 addition & 1 deletion packages/utils/src/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from './abRandom';
export * from './asyncNoop';
export * from './deepCopy';
export * from './cloneDeep';
export * from './delay';
export * from './getUniqId';
export * from './getUniqTime';
Expand Down
4 changes: 2 additions & 2 deletions packages/utils/src/common/wrapInArray/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { deepCopy } from '../deepCopy';
import { cloneDeep } from '../cloneDeep';

export function wrapInArray<T>(value: T | T[]): T[] {
const copiedValue = deepCopy(value);
const copiedValue = cloneDeep(value);

return Array.isArray(copiedValue) ? copiedValue : [copiedValue];
}
4 changes: 3 additions & 1 deletion packages/utils/src/object/omit/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { cloneDeep } from '../../common';

/**
* @description 주어진 객체에서 지정된 키들을 제거한 새로운 객체를 반환하는 함수입니다.
*
Expand All @@ -17,7 +19,7 @@ export function omit<T extends Record<PropertyKey, any>, K extends keyof T>(
obj: T,
keys: K[] | readonly K[]
): Omit<T, K> {
const result = { ...obj };
const result = cloneDeep(obj);

for (let i = 0; i < keys.length; i++) {
const key = keys[i];
Expand Down
4 changes: 3 additions & 1 deletion packages/utils/src/object/pick/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { cloneDeep } from '../../common';

/**
* @description 주어진 객체에서 지정된 키들만을 선택하여 새로운 객체를 반환하는 함수입니다.
*
Expand All @@ -23,7 +25,7 @@ export function pick<T extends Record<PropertyKey, any>, K extends keyof T>(
keys: K[] | readonly K[]
): Pick<T, K> {
const result = {} as T;
const copiedObj = { ...obj };
const copiedObj = cloneDeep(obj);

for (let i = 0; i < keys.length; i++) {
const key = keys[i];
Expand Down

0 comments on commit a267fda

Please sign in to comment.