Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compat): implement methodOf #907

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -207,6 +207,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
Loading