柯理化函数和组合函数都归属于函数式编程,用于解决函数式编程的问题
首先先说明一下函数式编程和面向对象式编程的优缺点
* 面向对象编程的
优点
程序更加便于分析、设计、理解
是易拓展的,由于继承、封装、多态的特性,
自然设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,而且成本较低
缺点
为了写可重用的代码而产生了很多无用的代码,导致代码膨胀
* 函数式编程的
优点
代码可读性更强。
实现同样的功能函数式编程所需要的代码比面向对象编程要少很多,
代码更加简洁明晰
开发速度快
缺点
所有的变量在程序运行期间都是一直存在的,非常占用运行资源
柯理化函数currying
- 概念:是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
示例:
//普通函数
function add(n1, n2) {
return n1 + n2
}
add(1, 2)
//柯理化函数
function add(n1) {//把参数一个一个的传入
return function (n2) {//返回带着返回结果的新函数
return n1 + n2
}
}
add(1)(2);
- 为什么使用柯理化函数
柯理化就是利用模块化思想处理多参函数,通过组合函数减少每个函数的入参数量,从而提高代码的可阅读性及可维护性。
创建一个柯理化函数
function currying(fun) {
var args = Array.prototype.slice.call(arguments, 1);
return function () {
var _args = args.concat(Array.prototype.slice.call(arguments));
return fun.apply(null, _args);
}
}
利用柯理化函数改造一个函数
function add(...vals){
return vals.reduce((pre, val) = > {
return pre + val;
});
}
var newAdd = currying(add, 1, 2, 3);
console.log(newAdd(4, 5, 6)); // 21
但是上边封装的柯理化函数的代码,只能使用一次,不能够多次执行
function currying(fn) {
var args = [].slice.call(arguments, 1);
return function () {
if (arguments.length == 0) {
return fn.apply(null, args);//如果不带参数调用,就说明参数已经传完了,要返回结果了
} else {
args = args.concat([].slice.call(arguments));//如果调用时带着参数,只需要把参数添加进去,利用不销毁作用域,下次再调用的时候就包含上次传递的参数了
}
}
}
多次调用
function add() {
var vals = Array.prototype.slice.call(arguments);
return vals.reduce((pre, val) = > {
return pre + val;
});
}
var newAdd = currying(add, 1, 2, 3);
newAdd(4, 5);
newAdd(6, 7);
console.log(newAdd()); // 28
//把每次函数调用的参数都存储起来,如果已无参形式调用,说明记录结束,需要做最终计算。
高级柯理化函数
function sum(a, b, c) {
return a + b + c;
}
sum(1,2,3)
//想实现一个柯理化函数
let curried= curry(sum);
curried(1,2,3) //6
curried(1)(2,3) //6
curried(1)(2)(3) //6
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
//如果传入的参数个数的和一直小于原函数的个数就递归执行,直到参数全部执行完就调用原函数
偏函数和柯理化函数的概念及区别
- 当把已知函数的一些参数固定,结果函数被称为偏函数,通过使用bind获得偏函数,也有其他方式实现。
eg: 当我们不想一次一次重复相同的参数时,偏函数是很便捷的。如我们有send(from,to)函数,如果from总是相同的,可以使用偏函数简化调用。 - 柯里化是转换函数调用从f(a,b,c)至f(a)(b)(c).Javascript通常既实现正常调用,也实现参数数量不足时的偏函数方式调用。
组合函数compose
简单来说,就是把很多函数组合到一起,执行一个函数,所有的函数都执行了
- 将函数串联起来执行,将多个函数组合起来,一个函数的输出结果是另一个函数的输入参数,一旦第一个函数开始执行,就会像多米诺骨牌一样推导执行了。
- 特点
compose的参数是函数,返回的也是一个函数
因为除了第一个函数的接受参数,其他函数的接受参数都是上一个函数的返回值
compsoe函数可以接受任意的参数,所有的参数都是函数,且执行方向是自右向左的,初始函数一定放到参数的最右面
示例
var greeting = (firstName, lastName) =>'hello, ' + firstName + ' ' + lastName + ' ';
var toUpper = str =>str.toUpperCase();
var fn = compose(toUpper, greeting);//从右向左执行
//只有greeting接受参数'jack', 'smith',toUpper 接受的参数是greeting的返回值
console.log(fn('jack', 'smith'))// ‘HELLO,JACK SMITH’
我使用的reduce实现的compose
function compose(){
let args=[].slice.call(arguments);
var length = args.length;
var index = length;
while (index--) {//如果参数中存在不是函数类型的值,就抛出错误
if (typeof args[index] !== 'function') {
throw new TypeError('Expected a function');
}
}
let last=args.pop();
args=args.reverse();
return function (){
let _args = [].slice.call(arguments);
return args.reduce((prev, next)=>{
return next(prev);//每一个函数执行的返回值都作为上一个函数的参数传进去
},last.apply(null,_args))//最后一个函数是需要传值的
}
}
继续执行
var trim = str =>str.replace(/\s+/g, '|');
var newFn = compose(trim, fn);
console.log(newFn('jack', 'smith'));
- loadsh的flow / flowRight实现 (利用while循环)
flow 返回一个函数,连续调用参数中传递的函数数组
//lodash的实现 从左向右执行的函数
var flow = function (funcs) {
var length = funcs.length;
var index = length
while (index--) {
if (typeof funcs[index] !== 'function') {
throw new TypeError('Expected a function');
}
}
return function (...args){
var index = 0
var result = length ? funcs[index].apply(this, args) : args[0]
while (++index < length) {
result = funcs[index].call(this, result)
}
return result
}
}
var flowRight = function (funcs) {
return flow(funcs.reverse())
}