您的第一个问题是您正在处理 pipe 的错误实现 - 第二个问题是新 JavaScript 中有多种扩展语法,并且(对于初学者)并不总是清楚哪个在哪里使用
休息参数
rest 参数将提供的参数收集到数组中的函数。这取代了 JavaScript 昔日的旧 arguments 对象
const f = (...xs) =>
xs
console.log(f()) // []
console.log(f(1)) // [1]
console.log(f(1,2)) // [1,2]
传播论据
spread arguments 允许您将数组(或任何可迭代的)作为参数传播给函数调用。这将替换(几乎所有)Function.prototype.apply
的实例
const g = (a,b,c) =>
a + b + c
const args = [1,2,3]
console.log(g(...args)) // 6
为什么 pipe 不好
这不是一个完整的函数——pipes 域是 [Function](函数数组),但如果使用空函数数组,此实现将产生错误 (TypeError: Reduce of empty array with no initial value)
这可能不会立即显现出来,但它可能以多种方式出现。最值得注意的是,当要应用的函数列表是一个在程序的其他地方创建的数组并最终为空时,Pipe 会发生灾难性的失败
const foo = Pipe()
foo(1)
// TypeError: Reduce of empty array with no initial value
const funcs = []
Pipe(...funcs) (1)
// TypeError: Reduce of empty array with no initial value
Pipe.apply(null, funcs) (1)
// TypeError: Reduce of empty array with no initial value
Pipe.call(null) (1)
// TypeError: Reduce of empty array with no initial value
重新实现pipe
这是无数实现之一,但它应该更容易理解。我们有一种使用 rest 参数和一种使用 spread 参数。最重要的是,pipe总是返回一个函数
const pipe = (f,...fs) => x =>
f === undefined ? x : pipe(...fs) (f(x))
const foo = pipe(
x => x + 1,
x => x * 2,
x => x * x,
console.log
)
foo(0) // 4
foo(1) // 16
foo(2) // 36
// empty pipe is ok
const bar = pipe()
console.log(bar(2)) // 2
“但我听说递归很糟糕”
好的,所以如果你要传递数千个函数,你可能会遇到堆栈溢出。在这种情况下,您可以像在原始帖子中一样使用堆栈安全的Array.prototype.reduce(或reduceRight)。
这次我将把问题分解成更小的部分,而不是在pipe 中做所有事情。每个部分都有不同的用途,pipe 现在只关心这些部分如何组合在一起。
const comp = (f,g) => x =>
f(g(x))
const identity = x =>
x
const pipe = (...fs) =>
fs.reduceRight(comp, identity)
const foo = pipe(
x => x + 1,
x => x * 2,
x => x * x,
console.log
)
foo(0) // 4
foo(1) // 16
foo(2) // 36
// empty pipe is ok
const bar = pipe()
console.log(bar(2)) // 2
“我真的只是想了解我帖子中的代码”
好的,让我们通过您的pipe 函数来看看发生了什么。因为reduce会多次调用reducing函数,所以我打算每次都对args使用唯一的重命名
// given
const Pipe = (...fns) => fns.reduce((f,g) => (...args) => g(f(...args)));
// evaluate
Pipe(a,b,c,d)
// environment:
fns = [a,b,c,d]
// reduce iteration 1 (renamed `args` to `x`)
(...x) => b(a(...x))
// reduce iteration 2 (renamed `args` to `y`)
(...y) => c((...x) => b(a(...x))(...y))
// reduce iteration 3 (renamed `args` to `z`)
(...z) => d((...y) => c((...x) => b(a(...x))(...y))(...z))
那么当应用该功能时会发生什么?让我们看看当我们将Pipe(a,b,c,d) 的结果与一些参数Q 一起应用时
// return value of Pipe(a,b,c,d) applied to `Q`
(...z) => d((...y) => c((...x) => b(a(...x))(...y))(...z)) (Q)
// substitute ...z for [Q]
d((...y) => c((...x) => b(a(...x))(...y))(...[Q]))
// spread [Q]
d((...y) => c((...x) => b(a(...x))(...y))(Q))
// substitute ...y for [Q]
d(c((...x) => b(a(...x))(...[Q]))
// spread [Q]
d(c((...x) => b(a(...x))(Q))
// substitute ...x for [Q]
d(c(b(a(...[Q])))
// spread [Q]
d(c(b(a(Q)))
所以,正如我们所料
// run
Pipe(a,b,c,d)(Q)
// evalutes to
d(c(b(a(Q))))
补充阅读
我写了很多关于函数组合的文章。我鼓励你探索其中一些相关的问题/我已经写了很多关于函数组合的文章。我鼓励您探索其中一些相关的问题/答案
如果有的话,您可能会在每个答案中看到 compose(或 pipe、flow 等)的不同实现。也许其中一位会向你的更高良心说话!