携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情
组合
函数式编程风格带给我最大的感受是,理解并编写一个单一功能的、满足的函数。然后就可以开始其他简单的组合。
链式编程
arr
.filter(/* ... */)
.map(i => { /* ... */ })
.unique()
.sort()
// ...
可能大家平时这种代码写得也不少,这种链式写法能够极大提高代码的可读性。
但是它也限制了代码的表现力,需要每个函数都返回数组,无法将不同的函数库的函数或者自定义函数连接在一起。
$('.list').width().height() // 报错
compose
函数式编程中的组合能够消除方法链存在的限制,使得任何函数的组合就更加灵活。
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)))
// compose 原理:
(f1, f2, f3, f4, ...reset) => value => f1( f2(f3(f4(value) )));
接下来看例子:
const CARS = [
{ name: "Ferrari FF", horsepower: 660, dollar_value: 700000, in_stock: true },
{ name: "Spyker C12 Zagato", horsepower: 650, dollar_value: 648000, in_stock: false },
{ name: "Jaguar XKR-S", horsepower: 550, dollar_value: 132000, in_stock: false },
{ name: "Audi R8", horsepower: 525, dollar_value: 114200, in_stock: false },
{ name: "Aston Martin One-77", horsepower: 750, dollar_value: 1850000, in_stock: true },
{ name: "Pagani Huayra", horsepower: 700, dollar_value: 1300000, in_stock: false }
];
获取数组最后一项,拿到它的 dollar_value 字段
last(list) 和 prop(item) 为 ramda 函数库内的 curry 包裹的函数,它们也就是你自己平时编写的函数
利用 curry,我们能够做到让每个函数都先接收数据,然后操作数据,最后再把数据传递到下一个函数那里去。
last 获取数组最后一项,prop 根据 key 获取属性值
// 不使用 compose
const getLastFn = function (cars) {
var last_car = _.last(cars);
return _.prop('in_stock', last_car);
};
getLastFn(CARS)
// 利用高阶函数 compose
const getLastFn = _.compose(_.prop('dollar_value'), _.last);
getLastFn(CARS)
我们现在有个需求是拿到 dollar_value 字段后添加美元符号 $
上述代码就变成了这样
function appendTag(str) {
return '$' + str
}
// 不使用 compose
const getLastFn = function (cars) {
var last_car = _.last(cars);
return appendTag(_.prop('in_stock', last_car));
};
getLastFn(CARS)
// 利用高阶函数 compose
const getLastFn = _.compose(appendTag, _.prop('dollar_value'), _.last);
getLastFn(CARS)
孰优孰劣现在应该很容易看出来,我们使用 compose 编写的代码后续维护起来更加方便,不需要去修改原有逻辑。
再来一个例子
var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);
shout("send in the clowns") // "SEND IN THE CLOWNS!"
shot函数现在是这样的
var shout = function(x){
return exclaim(toUpperCase(x));
};
函数内部是从右向左运行,而不是由内向外,其实组合就是一个 结合律 的特性
compose(toUpperCase, compose(head, reverse));
// 或者
compose(compose(toUpperCase, head), reverse);
因为如何为 compose 的调用分组不重要,所以结果都是一样的。运用结合律能为我们带来强大的灵活性。
compose 函数可看看经典工具库 lodash 和 ramda 是如何实现的,自己也试着实现一下。