【问题标题】:I need help understanding the rest and spread operator我需要帮助了解其余和传播运算符
【发布时间】:2017-05-31 04:00:52
【问题描述】:

这是代码:

const Pipe = (...fns) => fns.reduce((f,g) => (...args) => g(f(...args)));

所以通过 (...fns) fns 参数变成了一个数组,对吗?在这部分:

 (f,g) => (...args)

args 是从哪里来的?是否有默认的 args 参数?我无法阅读这部分:

(...args) => g(f(...args))

我无法用这种嵌套来解决我的问题,reduce 在这里所做的事情是如此令人困惑。

【问题讨论】:

  • 请注意,您已经两次询问了 (...args),并将其与代码的两个不同部分相关联。 (...args) => g(f(...args)) 是一个函数,它有一个名为 args 的参数(没有默认的 args)。当您询问(f,g) => (...args) 时,您将原始行拆分到错误的位置。添加一些额外的括号可能会有所帮助:(f,g) => ( (...args) => g(f(...args)) ) 以查看事物的分组方式。在上面的代码中,.reduce() 接受一个参数,该参数是一个返回另一个函数的函数。

标签: javascript ecmascript-6


【解决方案1】:

您的第一个问题是您正在处理 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(或 pipeflow 等)的不同实现。也许其中一位会向你的更高良心说话!

【讨论】:

  • 当你说“传播参数”时,它真的应该被称为“休息参数”。
【解决方案2】:

为了更好地理解,我已将代码翻译成块级别,如下所示,试图为新手开发人员解释有问题的代码,这会有所帮助。为了更好地实施,@naomik 有解决方案。

ES6

const Pipe = (...fns) => {
    return fns.reduce((f, g) => {
        return (...args) => {
            return g(f(...args))
        }
    })
};

等效的 ES5 实现:

var Pipe = function () {
    var fns = [];
    for (var _i = 0; _i < arguments.length; _i++) {
        fns[_i] = arguments[_i];
    }
    return fns.reduce(function (f, g) {
        return function () {
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                args[_i] = arguments[_i];
            }
            return g(f.apply(void 0/*Undefined or can use 'null'*/, args));
        };
    });
};

部分分解:(阅读代码cmets更好理解)

const inc = (num) => num+1 //Function to increment the number
const dbl = (num) => num*2 //Function double's the number
const sqr = (num) => num*num //Function Square's the number

/*Function breakdown*/
const _pipe = (f, g) => (...args) => g(f(...args)); //Second part
const Pipe = (...fns) => fns.reduce(_pipe); //First part

const incDblSqr = Pipe(inc, dbl, sqr) //Piping all the functions
const result = incDblSqr(2) // Piped function
console.log(result) // ((2+1)*2) ^ 2  = 36

说明:

有问题的代码有助于将结果从一个函数传递到另一个函数。

上述代码的分步流程:

  • 第一个数字加 1
  • 累加后的结果并通过管道传送到另一个 数字乘以 2(加倍)的函数
  • 最后加倍的数字是下一个函数的平方。

所有这些都是使用可以访问归约参数的闭包实现的(请阅读下面链接的文章以了解清楚)

结论: 有问题的代码有助于通过管道传递对前一个函数发出的结果进行操作的 n 个函数。


Andrew L. Van Slaars 的最佳文章: https://vanslaars.io/post/create-pipe-function/

为了清楚起见,请阅读上面的文章以及@naomik 解决方案

【讨论】:

    【解决方案3】:

    好吧,@naomik 以几乎相同的答案击败了我,但我想我仍然会分享我必须帮助您解释该函数如何以一种可能不那么神秘的方式工作的内容。 (也许)

    我想你已经知道“...”是如何工作的(如果你不知道,那么 naomik 的回答应该会有所帮助:D)

    这是相同管道函数的另一个版本,希望重新编写以更好地解释使用赋值来解释这一点的情况。

    Array.prototype.reduce 多次调用“reducer”toASingleFunction——functionsToPipe 中的每个函数调用一次。 currentFunctionToPipe 首先是 x =&gt; x + 1 然后是 x =&gt; x * 2 等等...

    newFunction 的第一个值是theIdentityFunction,reducer 返回另一个函数nextNewFunction。顾名思义,它成为下一次调用“reducer”(toASingleFunction)中的下一个newFunction

    一旦functionsToPipe 中的所有项目都被缩减,最终的newFunction 将返回为finalPipeFunction

    /**
     * `x` goes to itself
     */
    const theIdentityFunction = x => x;
    
    /**
     * the `reducer` that reduces the array of functions into a single function
     * using the value from the last function as the input to the next function
     */
    const toASingleFunction = (newFunction, currentFunctionToPipe) => {
      const nextNewFunction = function(value) { // i'm not using arrow functions here just to explicitly show that `nextNewFunction` is, in fact, a function
        const valueFromLastFunction = newFunction(value);
        return currentFunctionToPipe(valueFromLastFunction);
      }
      return nextNewFunction;
    };
    
    const pipe = (...functionsToPipe) => {
      const finalPipeFunction = functionsToPipe.reduce(toASingleFunction, /* start with */ theIdentityFunction);
    
      return finalPipeFunction;
    }
    
    const f = pipe(
      x => x + 1,
      x => x * 2,
      x => x * x
    );
    
    console.log(f(2)) // ((2 + 1) * 2) ^ 2 === 36

    也许这有帮助?

    祝你好运!

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-11
    • 1970-01-01
    • 2017-06-19
    • 2016-05-03
    • 1970-01-01
    • 2021-01-18
    • 1970-01-01
    相关资源
    最近更新 更多