给你一个全是整数的数组,要求过滤出所有的偶数,然后将它们翻倍,你会怎么写?

最简单的就是用 for 循环

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]
const results = []

for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    results.push( numbers[i] * 2 )
  }
}
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]
const results = []

for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    results.push( numbers[i] * 2 )
  }
}

这段代码很简单,但是如果不仔细读代码,你完全不知道这段代码的目的是什么。

我们可以采用更加声明式的写法

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]
const results = numbers
  .filter(n => n % 2 === 0)
  .map(n => n * 2)
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]
const results = numbers
  .filter(n => n % 2 === 0)
  .map(n => n * 2)

这段代码清晰多了,一眼就能看出来在干什么,但是它很慢,因为他需要遍历两次数组。

我们可以使用 transducer 来兼顾性能和可读性。transducer 就是高阶 reducer,它入参是一个 reducer,返回一个 reducer。

type Reducer<T = unknown, U = unknown> = (acc: T, cur: U) => T;
type Transducer = (r: Reducer) => Reducer;
type MapFn<T, U> = (a: T) => U;
type Predicate<T> = (a: T) => boolean;

const curry = (
  f: (...args: unknown[]) => unknown,
  arr: unknown[] = []
) => (...args: unknown[]) => (
  a => a.length === f.length ?
    f(...a) :
    curry(f, a)
)([...arr, ...args]);

const pipe = (...fns: Array<(a: unknown) => unknown>) =>
  (x: unknown) => fns.reduceRight((y, f) => f(y), x);

const mapTransducer = <T, U, R>(fn: MapFn<T, U>) =>
  (next: Reducer<R, U>) =>
    (acc: R, cur: T) => next(acc, fn(cur))

const filterTransducer = <T, U>(fn: Predicate<T>) =>
  (next: Reducer<U, T>) =>
    (acc: U, cur: T) =>
      fn(cur) ? next(acc, cur) : acc

const isEven = (n: number) => n % 2 === 0
const double = (n: number) => n * 2

const arrayConcat: Reducer = <T>(a: T[], c: T) => a.concat([c])

const doubleEvens = pipe(
  filterTransducer(isEven),
  mapTransducer(double)
);

const xform = doubleEvens(arrayConcat);

const data = [1, 2, 3, 4, 5, 6]
const result = data.reduce(xform, []); // [4, 8, 12]

// 上面的代码等价于
// const result = data.reduce((result, cur) => {
//     return isEven(cur) ? arrayConcat(result, double(cur)) : result
// }, [])

const transduce = curry(
  <T, U>(reducer: Reducer<U, T>, initial: U, source: T[]) => {
    return source.reduce(reducer, initial)
  }
)

// 只会遍历一遍数组
console.log(transduce(xform, [], data)) // [4, 8, 12]
type Reducer<T = unknown, U = unknown> = (acc: T, cur: U) => T;
type Transducer = (r: Reducer) => Reducer;
type MapFn<T, U> = (a: T) => U;
type Predicate<T> = (a: T) => boolean;

const curry = (
  f: (...args: unknown[]) => unknown,
  arr: unknown[] = []
) => (...args: unknown[]) => (
  a => a.length === f.length ?
    f(...a) :
    curry(f, a)
)([...arr, ...args]);

const pipe = (...fns: Array<(a: unknown) => unknown>) =>
  (x: unknown) => fns.reduceRight((y, f) => f(y), x);

const mapTransducer = <T, U, R>(fn: MapFn<T, U>) =>
  (next: Reducer<R, U>) =>
    (acc: R, cur: T) => next(acc, fn(cur))

const filterTransducer = <T, U>(fn: Predicate<T>) =>
  (next: Reducer<U, T>) =>
    (acc: U, cur: T) =>
      fn(cur) ? next(acc, cur) : acc

const isEven = (n: number) => n % 2 === 0
const double = (n: number) => n * 2

const arrayConcat: Reducer = <T>(a: T[], c: T) => a.concat([c])

const doubleEvens = pipe(
  filterTransducer(isEven),
  mapTransducer(double)
);

const xform = doubleEvens(arrayConcat);

const data = [1, 2, 3, 4, 5, 6]
const result = data.reduce(xform, []); // [4, 8, 12]

// 上面的代码等价于
// const result = data.reduce((result, cur) => {
//     return isEven(cur) ? arrayConcat(result, double(cur)) : result
// }, [])

const transduce = curry(
  <T, U>(reducer: Reducer<U, T>, initial: U, source: T[]) => {
    return source.reduce(reducer, initial)
  }
)

// 只会遍历一遍数组
console.log(transduce(xform, [], data)) // [4, 8, 12]

参考