javascript - 无法理解这个 compose reduce javascript 示例中发生了什么?
问题描述
var user1 = {
name: 'Nady',
active: true,
cart: [],
purchase: [],
};
var compose = function test1(f, g) {
return function test2(...args) {
return f(g(...args));
};
};
function userPurchase(...fns) {
return fns.reduce(compose);
}
userPurchase(
empty,
addItemToPurchase,
applayTax,
addItemToCart
)(user1, { name: 'laptop', price: 876 });
function addItemToCart(user, item) {
return { ...user, cart: [item] };
}
function applayTax(user) {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart.map(function updateCartItem(item) {
return { ...item, price: item.price * taxRate };
});
return { ...user, cart: updatedCart };
}
function addItemToPurchase(user) {
return { ...user, purchase: user.cart };
}
function empty(user) {
return { ...user, cart: [] };
}
我不太理解这个例子。我尝试使用调试器单步执行它并得出以下结论:
当我调用函数时,userPurchase
该函数将起作用,并reduce
在其结束时f
将作为累积返回。然后我们称它为参数传递,并在其中被调用。test2
g
addItemToCart
test2
(user1, { name: 'laptop', price: 876 })
g
addItemToCart
我不明白如何g
更改为applayTax
, then addItemToPurchase
, 然后empty
每次函数test2
调用本身。
这是如何或为什么会发生的?
解决方案
The thing that may have gotten you confused is taking the term accumulator
literally. By convention that's the name of the first argument to a reducer. But it's not necessary to use it to accumulate a value. In this case it is used to compose a series of functions.
The real meaning of the first argument to a reducer is previouslyReturnedValue
:
function compose(previouslyReturnedValue, g) {
return function (...args) {
return previouslyReturnedValue(g(...args));
};
}
So let's walk through this loop:
[empty, addItemToPurchase, applayTax, addItemToCart].reduce(
(f,g) => {
return (...args) => {
return f(g(...args));
}
}
);
The first round of the loop, f = empty
and g = addItemToPurchase
. This will cause compose
to return:
return (...args) => {
return empty(addItemToPurchase(...args));
}
Leaving the array to become: [applayTax, addItemToCart]
The second round of the loop f = (...args) => {return empty(addItemToPurchase(...args))}
and g = applyTax
. This will cause compose
to return:
return (...args) => {
return empty(addItemToPurchase(applyTax(...args)));
}
We continue with this logic until we finally get compose
to return the full chain of functions:
return (...args) => {
return empty(addItemToPurchase(applyTax(addItemToCart(...args))));
}
Alternate view of the same process
If the above is a bit hard to follow then let's name the anonymous function that becomes f
in each loop.
In the first round we get:
function f1 (...args) {
return empty(addItemToPurchase(...args));
}
In the second round we get:
function f2 (...args) {
return f1(applyTax(...args));
}
In the final round we get:
function f3 (...args) {
return f2(addItemToCart(...args));
}
It is this function f3
that is returned by reduce()
. So when you call the return value of reduce it will try to do:
f2(addItemToCart(...args))
Which will call addItemToCart()
and then call f2
which will execute:
f1(applyTax(...args))
Which will call applyTax()
and then call f1
which will execute:
empty(addItemToPurchase(...args))
Which will call addItemToPurchase()
and then call empty()
TLDR
Basically all this is doing:
let tmp;
tmp = addItemToCart(args);
tmp = applyTax(tmp);
tmp = addItemToPurchase(tmp);
tmp = empty(tmp);
More readable version
There is a way to implement this logic which is more readable and easier to understand if we abandon reduce()
. I personally like the functional array methods like map()
and reduce()
but this is one of those rare situations where a regular for
loop may lead to much more readable and debuggable code. Here's a simple alternative implementation that does exactly the same thing:
function userPurchase(...fns) {
return function(...args) {
let result = args;
// The original logic apply the functions in reverse:
for (let i=fns.length-1; i>=0; i--) {
let f = fns[i];
result = f(result);
}
return result;
}
}
Personally I find the implementation of userPurchase
using a for
loop much more readable than the reduce
version. It clearly loops through the functions in reverse order and keep calling the next function with the result of the previous function.
推荐阅读
- linux - 用条件检查目录名称的 Bash 脚本
- python - Python中的模板类
- javascript - 如何制作圆角和弯曲边的connect4板?
- python - 执行期间 PyCharm 中的 Yaml 警告
- svn - 将Subversion中旧删除的孤立文件合并到同一文件的新版本中
- python - 使用多列时如何“删除.().where”
- javascript - Wlada 轮播 3d Vue.js
- flutter - 是否有相当于“异步”的 SynchronousFuture
- mysql - MySql:在子查询中引用主查询中的列
- javascript - Rails 6:SimpleFrom Remote:通过 javascript/Jquery 真正提交