Skip to content

Commit

Permalink
feat(compat): implement methodOf (#907)
Browse files Browse the repository at this point in the history
* feat(compat): implement methodOf

* make lint happy

* Update docs/ko/reference/compat/util/methodOf.md

* Update docs/ja/reference/compat/util/methodOf.md

* Update docs/ko/reference/compat/util/methodOf.md

---------

Co-authored-by: Sojin Park <raon0211@toss.im>
Co-authored-by: Sojin Park <raon0211@gmail.com>
  • Loading branch information
3 people authored Dec 18, 2024
1 parent 85d58ec commit 984d567
Show file tree
Hide file tree
Showing 13 changed files with 394 additions and 1 deletion.
24 changes: 24 additions & 0 deletions benchmarks/performance/method.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { bench, describe } from 'vitest';
import { method as methodToolkit_ } from 'es-toolkit/compat';
import { method as methodLodash_ } from 'lodash';

const methodToolkit = methodToolkit_;
const methodLodash = methodLodash_;

const object = {
a: {
b: function (x: number, y: number) {
return x + y;
},
},
};

describe('method', () => {
bench('es-toolkit/compat', () => {
methodToolkit('a.b', 1, 2)(object);
});

bench('lodash', () => {
methodLodash('a.b', 1, 2)(object);
});
});
24 changes: 24 additions & 0 deletions benchmarks/performance/methodOf.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { bench, describe } from 'vitest';
import { methodOf as methodOfToolkit_ } from 'es-toolkit/compat';
import { methodOf as methodOfLodash_ } from 'lodash';

const methodOfToolkit = methodOfToolkit_;
const methodOfLodash = methodOfLodash_;

const object = {
a: {
b: function (x: number, y: number) {
return x + y;
},
},
};

describe('methodOf', () => {
bench('es-toolkit/compat', () => {
methodOfToolkit(object, 1, 2)('a.b');
});

bench('lodash', () => {
methodOfLodash(object, 1, 2)('a.b');
});
});
39 changes: 39 additions & 0 deletions docs/ja/reference/compat/util/methodOf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# methodOf

::: info
この関数は互換性のために `es-toolkit/compat` からのみインポートできます。代替可能なネイティブ JavaScript API があるか、まだ十分に最適化されていないためです。

`es-toolkit/compat` からこの関数をインポートすると、[lodash と完全に同じように動作](../../../compatibility.md)します。
:::

指定された`object`のパスにあるメソッドを、提供された引数で呼び出す関数を作成します。

## インターフェース

```typescript
function methodOf(object: object, ...args: any[]): (path: PropertyKey | PropertyKey[]) => any;
```

### パラメータ

- `object` (`object`): 要検索のオブジェクト。
- `args` (`...any`): メソッドを呼び出す際に使用する引数。

### 戻り値

(`(path: PropertyKey | PropertyKey[]) => any`): パスを受け取り、`object``path``args` でメソッドを呼び出す新しい関数。

##

```typescript
const object = {
a: {
b: function (x, y) {
return x + y;
},
},
};

const add = methodOf(object, 1, 2);
console.log(add('a.b')); // => 3
```
39 changes: 39 additions & 0 deletions docs/ko/reference/compat/util/methodOf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# methodOf

::: info
이 함수는 호환성을 위한 `es-toolkit/compat` 에서만 가져올 수 있어요. 대체할 수 있는 네이티브 JavaScript API가 있거나, 아직 충분히 최적화되지 않았기 때문이에요.

`es-toolkit/compat`에서 이 함수를 가져오면, [lodash와 완전히 똑같이 동작](../../../compatibility.md)해요.
:::

주어진 경로에 있는 객체의 메서드를 제공된 파라미터로 호출하는 함수를 만들어요.

## 인터페이스

```typescript
function methodOf(object: object, ...args: any[]): (path: PropertyKey | PropertyKey[]) => any;
```

### 파라미터

- `object` (`object`): 조회할 객체.
- `args` (`...any`): 메서드를 호출할 때 사용할 인수.

### 반환 값

(`(path: PropertyKey | PropertyKey[]) => any`): 경로를 받아 `object``path`에서 `args`로 메서드를 호출하는 새로운 함수.

## 예시

```typescript
const object = {
a: {
b: function (x, y) {
return x + y;
},
},
};

const add = methodOf(object, 1, 2);
console.log(add('a.b')); // => 3
```
39 changes: 39 additions & 0 deletions docs/reference/compat/util/methodOf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# methodOf

::: info
This function is only available in `es-toolkit/compat` for compatibility reasons. It either has alternative native JavaScript APIs or isn’t fully optimized yet.

When imported from `es-toolkit/compat`, it behaves exactly like lodash and provides the same functionalities, as detailed [here](../../../compatibility.md).
:::

Creates a function that invokes the method at a given path of `object` with the provided arguments.

## Signature

```typescript
function methodOf(object: object, ...args: any[]): (path: PropertyKey | PropertyKey[]) => any;
```

### Parameters

- `object` (`object`): The object to query.
- `args` (`...any`): The arguments to invoke the method with.

### Returns

(`(path: PropertyKey | PropertyKey[]) => any`): Returns a new function that takes a path and invokes the method at `path` of `object` with `args`.

## Examples

```typescript
const object = {
a: {
b: function (x, y) {
return x + y;
},
},
};

const add = methodOf(object, 1, 2);
console.log(add('a.b')); // => 3
```
39 changes: 39 additions & 0 deletions docs/zh_hans/reference/compat/util/methodOf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# methodOf

::: info
出于兼容性原因,此函数仅在 `es-toolkit/compat` 中提供。它可能具有替代的原生 JavaScript API,或者尚未完全优化。

`es-toolkit/compat` 导入时,它的行为与 lodash 完全一致,并提供相同的功能,详情请见 [这里](../../../compatibility.md)
:::

创建一个函数,该函数使用提供的参数调用指定`object`路径上的方法。

## 签名

```typescript
function methodOf(object: object, ...args: any[]): (path: PropertyKey | PropertyKey[]) => any;
```

### 参数

- `object` (`object`): 要查询的对象。
- `args` (`...any`): 用来调用方法的参数。

### 返回值

(`(path: PropertyKey | PropertyKey[]) => any`): 返回一个新函数,该函数接受一个路径,并用`args``object``path`调用方法。

## 示例

```typescript
const object = {
a: {
b: function (x, y) {
return x + y;
},
},
};

const add = methodOf(object, 1, 2);
console.log(add('a.b')); // => 3
```
3 changes: 3 additions & 0 deletions src/compat/_internal/stubFour.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const stubFour = function () {
return 4;
};
3 changes: 3 additions & 0 deletions src/compat/_internal/stubThree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const stubThree = function () {
return 3;
};
3 changes: 3 additions & 0 deletions src/compat/_internal/stubTwo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const stubTwo = function () {
return 2;
};
1 change: 1 addition & 0 deletions src/compat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export { iteratee } from './util/iteratee.ts';
export { lt } from './util/lt.ts';
export { lte } from './util/lte.ts';
export { method } from './util/method.ts';
export { methodOf } from './util/methodOf.ts';
export { now } from './util/now.ts';
export { stubArray } from './util/stubArray.ts';
export { stubFalse } from './util/stubFalse.ts';
Expand Down
1 change: 0 additions & 1 deletion src/compat/util/method.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { invoke } from './invoke.ts';
import { flatten } from '../array/flatten.ts';

/**
* Creates a function that invokes the method at `path` of a given object with the provided arguments.
Expand Down
154 changes: 154 additions & 0 deletions src/compat/util/methodOf.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { describe, expect, it } from 'vitest';
import { constant, each, map, noop } from '..';
import { methodOf as methodOfToolkit } from './methodOf';
import { times } from './times';
import { stubFour } from '../_internal/stubFour';
import { stubOne } from '../_internal/stubOne';
import { stubThree } from '../_internal/stubThree';
import { stubTwo } from '../_internal/stubTwo';

describe('methodOf', () => {
it('should create a function that calls a method of a given key', () => {
const object = { a: stubOne };

each(['a', ['a']], path => {
const methodOf = methodOfToolkit(object);
expect(methodOf.length).toBe(1);
expect(methodOf(path)).toBe(1);
});
});

it('should work with deep property values', () => {
const object = { a: { b: stubTwo } };

each(['a.b', ['a', 'b']], path => {
const methodOf = methodOfToolkit(object);
expect(methodOf(path)).toBe(2);
});
});

it('should work with a non-string `path`', () => {
const array = times(3, constant);

each([1, [1]], path => {
const methodOf = methodOfToolkit(array);
expect(methodOf(path)).toBe(1);
});
});

it('should coerce `path` to a string', () => {
function fn() {}
fn.toString = constant('fn');

const expected = [1, 2, 3, 4];
const object = {
null: stubOne,
undefined: stubTwo,
fn: stubThree,
'[object Object]': stubFour,
};
const paths = [null, undefined, fn, {}];

times(2, index => {
const actual = map(paths, path => {
const methodOf = methodOfToolkit(object);
// @ts-expect-error - methodOf should handle nullish values
return methodOf(index ? [path] : path);
});

expect(actual).toEqual(expected);
});
});

it('should work with inherited property values', () => {
function Foo() {}
Foo.prototype.a = stubOne;

each(['a', ['a']], path => {
// @ts-expect-error - Foo is a constructor
const methodOf = methodOfToolkit(new Foo());
expect(methodOf(path)).toBe(1);
});
});

it('should use a key over a path', () => {
const object = { 'a.b': stubOne, a: { b: stubTwo } };

each(['a.b', ['a.b']], path => {
const methodOf = methodOfToolkit(object);
expect(methodOf(path)).toBe(1);
});
});

it('should return `undefined` when `object` is nullish', () => {
// eslint-disable-next-line no-sparse-arrays
const values = [, null, undefined];
const expected = map(values, noop);

each(['constructor', ['constructor']], path => {
const actual = map(values, (value, index) => {
// @ts-expect-error - methodOf should handle nullish values
const methodOf = index ? methodOfToolkit() : methodOfToolkit(value);
return methodOf(path);
});

expect(actual).toEqual(expected);
});
});

it('should return `undefined` for deep paths when `object` is nullish', () => {
// eslint-disable-next-line no-sparse-arrays
const values = [, null, undefined];
const expected = map(values, noop);

each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], path => {
const actual = map(values, (value, index) => {
// @ts-expect-error - methodOf should handle nullish values
const methodOf = index ? methodOfToolkit() : methodOfToolkit(value);
return methodOf(path);
});

expect(actual).toEqual(expected);
});
});

it('should return `undefined` if parts of `path` are missing', () => {
const object = {};
const methodOf = methodOfToolkit(object);

each(['a', 'a[1].b.c', ['a'], ['a', '1', 'b', 'c']], path => {
expect(methodOf(path)).toBe(undefined);
});
});

it('should apply partial arguments to function', () => {
const object = {
fn: function () {
// eslint-disable-next-line prefer-rest-params
return Array.prototype.slice.call(arguments);
},
};

const methodOf = methodOfToolkit(object, 1, 2, 3);

each(['fn', ['fn']], path => {
expect(methodOf(path)).toEqual([1, 2, 3]);
});
});

it('should invoke deep property methods with the correct `this` binding', () => {
const object = {
a: {
b: function () {
return this.c;
},
c: 1,
},
};
const methodOf = methodOfToolkit(object);

each(['a.b', ['a', 'b']], path => {
expect(methodOf(path)).toBe(1);
});
});
});
Loading

0 comments on commit 984d567

Please sign in to comment.