简体   繁体   English

在函数数组上执行Javascript'reduce'如何实现函数组合?

[英]How Javascript `reduce` performed on function array achieves function composition?

I came across this pattern in redux compose function. 我在redux compose功能中遇到了这种模式。 I still don't understand how in the example below the functions are evaluated starting from the last and not from the first: 我仍然不明白如何在下面的示例中从最后开始而不是从第一个开始评估函数:

function f2(a) {
  return a + a;
}
function f3(a) {
  return a + a + a;
}
function f4(a) {
  return a + a + a + a;
}
function f5(a) {
  return a + a + a + a + a;
}

function compose(...funcs) {
  return funcs.reduce(function x(a, b) {
    return function y(...args) {
      const temp = a(b(...args));
      return temp;
    };
  });
}

const composedFunction = compose(f2, f3, f4, f5);
const result = composedFunction(2);

In the first reduce iteration the accumulator is f2 so we'll get f2(f3(2))=12 . 在第一个reduce迭代中,累加器为f2因此我们得到f2(f3(2))=12 In the next iteration we'll call f4(12)=48 . 在下一次迭代中,我们将调用f4(12)=48 In the last iteration we'll call f5(48)=240 . 在上一次迭代中,我们将调用f5(48)=240 So the evaluation order is f5(f4(f2(f3(2)))) . 因此,评估顺序为f5(f4(f2(f3(2)))) But using console.log I see that the evaluation order is f2(f3(f4(f5(2)))) which is also 240 by coincidence. 但是使用console.log我看到评估顺序是f2(f3(f4(f5(2)))) ,巧合的也是240。

As far as I understand the function y is called for all array elements so why only the last function gets 2 as the parameter? 据我了解,函数y是为所有数组元素调用的,那么为什么只有最后一个函数将2作为参数?

Let's step through the code with a very simple example: 让我们通过一个非常简单的示例逐步完成代码:

 compose(f2, f3, f4)

As no initial value was passed to reduce, it will start with the first (f2) and the second (f3) value of the array and call the callback with that, x gets called with a being f2 and b being f3 . 由于没有传递任何初始值来减少值,因此它将以数组的第一个(f2)和第二个(f3)值开头,并以该值调用回调, x的调用为af2bf3 Now x does'nt do anything, it just returns function y that can access a and b through a closure. 现在x不执行任何操作,只返回可以通过闭包访问a和b的函数y

Reduce will now continue to the third element, the first argument being the result of the previous callback (the closured y ), and the second argument being f4 . Reduce现在将继续到第三个元素,第一个参数是上一个回调(闭包y )的结果,第二个参数是f4 Now x gets called again, and another closure is created over y , y gets the finally returned from the whole function. 现在再次调用x ,并在y上创建另一个闭包, y从整个函数中最终返回。

If we try to visualize thus closured function it'll be: 如果我们尝试可视化这样的闭包函数,它将是:

 y { // closure of y
  a -> y { // a references another closure of y
    a -> f3,
    b -> f2
  },
  b -> f4
}

Now you call that closured y and pass 2 into it, that will call b ( f4 ) and pass the result to the call to a (closured y). 现在,您调用该闭包y并将2传递给它,这将调用bf4 )并将结果传递给该调用到a (闭包y)。

 a         (  b(...args))   
 y { ... } (  f4(2) )

Now that closured y will do the same: 现在,封闭的y将执行相同的操作:

 a (  b ( ...args))
 f2( f3( f4( 2 ) ) )

Hint: It is sometimes really difficult to keep track of closured values, therefore the console provides you with great utilities to keep track of them: Open your code in the consoles "debugger" tab, click on the line numbers where the function calls are to attach breakpoints, then run the code again, the execution will yield whenever a breakpoint is reached and you can see the values of all variables (including closured ones). 提示:有时很难跟踪闭包的值,因此控制台为您提供了强大的实用程序来跟踪它们:在控制台的“ debugger”选项卡中打开代码,单击函数调用所在的行号。附加断点,然后再次运行代码,只要到达断点,执行就会产生,您可以看到所有变量(包括封闭变量)的值。

The reduce is not calling the functions f2, f3, f3, f5, but it is creating a function from those. reduce不会调用函数f2,f3,f3,f5,而是从这些函数创建函数。 This is the value of the accumulator in each iteration. 这是每次迭代中累加器的值。 Note that the value is a function and not a result from execution of the function. 请注意,该值是一个function而不是function执行的结果。

1:a=f2;b=f3;return value(NOT TEMP but function y)=f2(f3(...args)) 1:a = f2; b = f3;返回值(不是温度,但函数y)= f2(f3(... args))

2:a(prev return value)=f2(f3(...args));b=f4;return value=f2(f3(f4(...args))) 2:a(上一个返回值)= f2(f3(... args)); b = f4;返回值= f2(f3(f4(... args))))

and so on.... 等等....

The compose function can be re-written as: 撰写功能可以重写为:

function compose(...funcs) {
  return funcs.reduce(function (a, b) {
    return function (arg) {
      const temp = a(b(arg));
      return temp;
    };
  });
}

After the first iteration, the returned function which is passed in as the next accumulator is: 第一次迭代后,作为下一个累加器传递的返回函数为:

function (arg) {       // R1
  return f2(f3(arg));

} }

After the second iteration, the returned function which is passed in as the next accumulator is: 在第二次迭代之后,作为下一个累加器传递的返回函数为:

function (arg) {       // R2
  return R1(f4(arg));

} }

And finally, the returned function assigned to composedFunction is: 最后,分配给compositionFunction的返回函数为:

function (arg) {       // composedFunction
  return R2(f5(arg));

} }

So running composedFunction(2) and going back up the chain: 因此,运行composedFunction(2)并返回上链:

f5(2) returns 10

R2(10) returns R1(f4(10))
which is       R1(40)

R1(40) returns f2(f3(40))
which is   f2(120)
which is   240

Hopefully that's sufficient. 希望这足够了。

It can be written as a single call as: 可以将其写为以下单个调用:

function composedFunction(arg) {
  return f2(f3(f4(f5(arg))));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM