feat(spread): Implement spread (#428)

* feat(spread): implement spread

* lodash compatibility

* remove useless comment
This commit is contained in:
D-Sketon 2024-08-31 18:17:53 +08:00 committed by GitHub
parent 5bccc517a1
commit 1c74848003
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 274 additions and 3 deletions

View File

@ -0,0 +1,24 @@
import { bench, describe } from 'vitest';
import { spread as spreadToolkit } from 'es-toolkit';
import { spread as spreadCompat } from 'es-toolkit/compat';
import { spread as spreadLodash } from 'lodash';
describe('spread', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function fn(_a: unknown, _b: unknown, _c: unknown) {
// eslint-disable-next-line prefer-rest-params
return Array.from(arguments);
}
bench('es-toolkit/spread', () => {
spreadToolkit(fn, 1);
});
bench('es-toolkit/compat/spread', () => {
spreadCompat(fn, 1);
});
bench('lodash/spread', () => {
spreadLodash(fn, 1);
});
});

View File

@ -134,6 +134,7 @@ function sidebar(): DefaultTheme.Sidebar {
{ text: 'partial', link: '/reference/function/partial' },
{ text: 'partialRight', link: '/reference/function/partialRight' },
{ text: 'rest', link: '/reference/function/rest' },
{ text: 'spread', link: '/reference/function/spread' },
],
},
{

View File

@ -131,6 +131,7 @@ function sidebar(): DefaultTheme.Sidebar {
{ text: 'partial', link: '/zh_hans/reference/function/partial' },
{ text: 'partialRight', link: '/zh_hans/reference/function/partialRight' },
{ text: 'rest', link: '/zh_hans/reference/function/rest' },
{ text: 'spread', link: '/zh_hans/reference/function/spread' },
],
},
{

View File

@ -0,0 +1,51 @@
# spread
Creates a function that invokes `func` with the `this` binding of the create function and an array of arguments much like [`Function#apply`](https://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.apply).
## Signature
```typescript
function spread<F extends (...args: any[]) => any>(func: F, startIndex: number = 0): (...args: any[]) => ReturnType<F>;
```
### Parameters
- `func` (`F`): The function to spread arguments over.
- `startIndex` (`number`, optional): The start position of the spread. Defaults to `0`.
### Returns
(`(...args: any[]) => ReturnType<F>`): A new function that invokes `func` with the spread arguments.
## Examples
```typescript
import { spread } from 'es-toolkit/function';
const say = spread(function (who, what) {
return who + ' says ' + what;
});
say(['fred', 'hello']);
// => 'fred says hello'
```
## Lodash Compatibility
Import `spread` from `es-toolkit/compat` for full compatibility with lodash.
- `spread` treats `startIndex` as `0` for negative or `NaN` values.
- `spread` accepts `startIndex` with fractional values, but coerces them to integers.
```typescript
import { spread } from 'es-toolkit/compat';
function fn(a: unknown, b: unknown, c: unknown) {
return Array.from(arguments);
}
spread(fn, -1)([1, 2]); // Returns [1, 2]
spread(fn, NaN)([1, 2]); // Returns [1, 2]
spread(fn, 'a')([1, 2]); // Returns [1, 2]
spread(fn, 1.6)(1, [2, 3]); // Returns [1, 2, 3]
```

View File

@ -53,10 +53,10 @@ console.log(chunk([1, 2, 3, 4, 5], 2));
## Lodash 兼容性
从`es-toolkit/compat`中导入`chunk`以实现与 lodash 的完全兼容。
`es-toolkit/compat` 中导入 `chunk` 以实现与 lodash 的完全兼容。
- chunk 不抛出异常,当给定的`size`小于 1 时。
- chunk 接受分数值,这些值将被向下舍入到最近的整数。
- `chunk` 当给定的 `size` 小于 1 时不抛出异常
- `chunk` 接受分数值,这些值将被向下舍入到最近的整数。
```typescript
import { chunk } from 'es-toolkit/compat';

View File

@ -0,0 +1,51 @@
# spread
创建一个函数,该函数调用 `func`,使用创建函数的 `this` 绑定和一个类似于 [`Function#apply`](https://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.apply) 的参数数组。
## 签名
```typescript
function spread<F extends (...args: any[]) => any>(func: F, startIndex: number = 0): (...args: any[]) => ReturnType<F>;
```
### 参数
- `func` (`F`): 用于展开参数的函数。
- `startIndex` (`number`, 可选): 参数的开始位置,默认为 `0`
### 返回值
(`(...args: any[]) => ReturnType<F>`): 一个新的函数,该函数使用展开的参数调用 `func`
## 示例
```typescript
import { spread } from 'es-toolkit/function';
const say = spread(function (who, what) {
return who + ' says ' + what;
});
say(['fred', 'hello']);
// => 'fred says hello'
```
## Lodash 兼容性
`es-toolkit/compat` 中导入 `spread` 以实现与 lodash 的完全兼容。
- `spread` 当给定的 `startIndex` 小于 0 或为 `NaN` 时将其视为 `0`
- `spread` 接受分数值的 `startIndex`,但将其强制转换为整数。
```typescript
import { spread } from 'es-toolkit/compat';
function fn(a: unknown, b: unknown, c: unknown) {
return Array.from(arguments);
}
spread(fn, -1)([1, 2]); // Returns [1, 2]
spread(fn, NaN)([1, 2]); // Returns [1, 2]
spread(fn, 'a')([1, 2]); // Returns [1, 2]
spread(fn, 1.6)(1, [2, 3]); // Returns [1, 2, 3]
```

View File

@ -0,0 +1,59 @@
import { describe, it, expect } from 'vitest';
import { spread } from './spread';
describe('spread', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function fn(_a: unknown, _b: unknown, _c: unknown) {
// eslint-disable-next-line prefer-rest-params
return Array.from(arguments);
}
it('should spread arguments to `func`', () => {
const spreadFn = spread(fn);
const expected = [1, 2];
expect(spreadFn([1, 2])).toEqual(expected);
expect(spreadFn([1, 2], 3)).toEqual(expected);
});
it('should accept a falsey `array`', () => {
const falsey = [null, undefined, false, 0, NaN, ''];
const spreadFn = spread(() => true);
const expected = falsey.map(() => true);
const actual = falsey.map(array => {
return spreadFn(array);
});
expect(actual).toEqual(expected);
});
it('should work with `startIndex`', () => {
const spreadFn = spread(fn, 1);
const expected = [1, 2, 3];
expect(spreadFn(1, [2, 3])).toEqual(expected);
expect(spreadFn(1, [2, 3], 4)).toEqual(expected);
});
it('should treat `start` as `0` for negative or `NaN` values', () => {
const values = [-1, NaN, 'a'];
const expected = values.map(() => [1, 2]);
const actual = values.map(value => {
// @ts-expect-error - spreadFn is not being called with the correct arguments
const spreadFn = spread(fn, value);
return spreadFn([1, 2]);
});
expect(actual).toEqual(expected);
});
it('should coerce `startIndex` to an integer', () => {
const spreadFn = spread(fn, 1.6);
const expected = [1, 2, 3];
expect(spreadFn(1, [2, 3])).toEqual(expected);
expect(spreadFn(1, [2, 3], 4)).toEqual(expected);
});
});

View File

@ -0,0 +1,22 @@
import { spread as spreadToolkit } from '../../function/spread.ts';
/**
* Creates a function that invokes `func` with the `this` binding of the create function and an array of arguments much like `Function#apply`.
*
* @template F The type of the function to spread arguments over.
* @param {F} func The function to spread arguments over.
* @param {number} startIndex The start position of the spread.
* @returns {(...args: any[]) => ReturnType<F>} A new function that invokes `func` with the spread arguments.
*/
export function spread<F extends (...args: any[]) => any>(
func: F,
startIndex: number = 0
): (...args: any[]) => ReturnType<F> {
startIndex = Number.parseInt(startIndex as any, 10);
if (Number.isNaN(startIndex) || startIndex < 0) {
startIndex = 0;
}
return spreadToolkit(func, startIndex);
}

View File

@ -42,6 +42,7 @@ export { indexOf } from './array/indexOf.ts';
export { ary } from './function/ary.ts';
export { bind } from './function/bind.ts';
export { rest } from './function/rest.ts';
export { spread } from './function/spread.ts';
export { get } from './object/get.ts';
export { set } from './object/set.ts';

View File

@ -11,3 +11,4 @@ export { unary } from './unary.ts';
export { partial } from './partial.ts';
export { partialRight } from './partialRight.ts';
export { rest } from './rest.ts';
export { spread } from './spread.ts';

View File

@ -0,0 +1,38 @@
import { describe, it, expect } from 'vitest';
import { spread } from './spread';
describe('spread', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function fn(_a: unknown, _b: unknown, _c: unknown) {
// eslint-disable-next-line prefer-rest-params
return Array.from(arguments);
}
it('should spread arguments to `func`', () => {
const spreadFn = spread(fn);
const expected = [1, 2];
expect(spreadFn([1, 2])).toEqual(expected);
expect(spreadFn([1, 2], 3)).toEqual(expected);
});
it('should accept a falsey `array`', () => {
const falsey = [null, undefined, false, 0, NaN, ''];
const spreadFn = spread(() => true);
const expected = falsey.map(() => true);
const actual = falsey.map(array => {
return spreadFn(array);
});
expect(actual).toEqual(expected);
});
it('should work with `startIndex`', () => {
const spreadFn = spread(fn, 1);
const expected = [1, 2, 3];
expect(spreadFn(1, [2, 3])).toEqual(expected);
expect(spreadFn(1, [2, 3], 4)).toEqual(expected);
});
});

22
src/function/spread.ts Normal file
View File

@ -0,0 +1,22 @@
/**
* Creates a function that invokes `func` with the `this` binding of the create function and an array of arguments much like `Function#apply`.
*
* @template F The type of the function to spread arguments over.
* @param {F} func The function to spread arguments over.
* @param {number} startIndex The start position of the spread.
* @returns {(...args: any[]) => ReturnType<F>} A new function that invokes `func` with the spread arguments.
*/
export function spread<F extends (...args: any[]) => any>(
func: F,
startIndex: number = 0
): (...args: any[]) => ReturnType<F> {
return function (this: any, ...args: any[]) {
const array = args[startIndex];
const params = args.slice(0, startIndex);
if (array) {
params.push(...array);
}
return func.apply(this, params);
};
}