函数式编程具备两个基本特征。编程
第一等公民是指函数跟其它的数据类型同样处于平等地位,能够赋值给其余变量,能够做为参数传入另外一个函数,也能够做为别的函数的返回值。数据结构
// 赋值 var a = function fn1() { } // 函数做为参数 function fn2(fn) { fn() } // 函数做为返回值 function fn3() { return function() {} }
纯函数是指相同的输入总会获得相同的输出,而且不会产生反作用的函数。app
从纯函数的概念咱们能够知道纯函数具备两个特色:dom
无反作用指的是函数内部的操做不会对外部产生影响(如修改全局变量的值、修改 dom 节点等)。函数式编程
// 是纯函数 function add(x,y){ return x + y } // 输出不肯定,不是纯函数 function random(x){ return Math.random() * x } // 有反作用,不是纯函数 function setColor(el,color){ el.style.color = color ; } // 输出不肯定、有反作用,不是纯函数 var count = 0; function addCount(x){ count+=x; return count; }
函数式编程具备两个最基本的运算:合成(compose)和柯里化(Currying)。函数
函数合成指的是将表明各个动做的多个函数合并成一个函数。
这里我直接给出通用 compose 函数的代码post
function compose() { var args = arguments; var start = args.length - 1; return function() { var i = start; var result = args[start].apply(this, arguments); while (i--) result = args[i].call(this, result); return result; }; }
让咱们来实践下上述通用的 compose 函数~优化
function addHello(str){ return 'hello '+str; } function toUpperCase(str) { return str.toUpperCase(); } function reverse(str){ return str.split('').reverse().join(''); } var composeFn=compose(reverse,toUpperCase,addHello); console.log(composeFn('ttsy')); // YSTT OLLEH
上述过程有三个动做,「hello」、「转换大写」、「反转」,能够看到经过 compose 将上述三个动做表明的函数合并成了一个,最终输出了正确的结果。this
在维基百科中对柯里化的定义是:在计算机科学中,柯里化,又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数并且返回结果的新函数的技术。prototype
柯里化函数则是将函数柯里化以后获得的一个新函数。由上述定义可知,柯里化函数有以下两个特性:
优化后的 createCurry 代码以下:
// 参数只能从左到右传递 function createCurry(func, arrArgs) { var args=arguments; var funcLength = func.length; var arrArgs = arrArgs || []; return function() { var _arrArgs = Array.prototype.slice.call(arguments); var allArrArgs=arrArgs.concat(_arrArgs) // 若是参数个数小于最初的func.length,则递归调用,继续收集参数 if (allArrArgs.length < funcLength) { return args.callee.call(this, func, allArrArgs); } // 参数收集完毕,则执行func return func.apply(this, allArrArgs); } }
优化以后的 createCurry 函数则显得更增强大
// createCurry 返回一个柯里化函数 var addCurry=createCurry(function(a, b, c) { return a + b + c; }); console.log(addCurry(1)(2)(3)); // 6 console.log(addCurry(1, 2, 3)); // 6 console.log(addCurry(1, 2)(3)); // 6 console.log(addCurry(1)(2, 3)); // 6
柯里化其实是把简答的问题复杂化了,可是复杂化的同时,咱们在使用函数时拥有了更加多的自由度。
在前面函数合成的例子中,执行了先「加上 4」再「乘以 4」的动做,咱们能够看到代码中是经过 multiply4(add4(1)) 这种形式来实现的,若是经过 compose 函数,则是相似于 compose(multiply4,add4)(1) 这种形式来实现代码。
而在函数式编程的思惟中,除了将动做抽象出来外,还但愿动做执行的顺序更加清晰,因此对于上面的例子来讲,更但愿是经过以下的形式来执行咱们的动做
fn(1).add4().multiply4()
这时咱们须要用到函子的概念。
function Functor(val){ this.val = val; } Functor.prototype.map=function(f){ return new Functor(f(this.val)); }
函子能够简单地理解为有用到 map 方法的数据结构。如上 Functor 的实例就是一个函子。
在函子的 map 方法中接受一个函数参数,而后返回一个新的函子,新的函子中包含的值是被函数参数处理事后返回的值。该方法将函子里面的每个值,映射到另外一个函子。
经过 Functor 函子,咱们能够经过以下的方式调用
console.log((new Functor(1)).map(add4).map(multiply4)) // Functor { val: 20 }
上述调用的方式是 (new Calculate(1)).map(add4).map(multiply4) ,跟咱们想要的效果已经差很少了,可是咱们不但愿有 new 的存在,因此咱们在 Functor 函子挂载上 of 方法
function Functor(val){ this.val = val; } Functor.prototype.map=function(f){ return new Functor(f(this.val)); } Functor.of = function(val) { return new Functor(val); }
最终咱们能够经过以下方式调用
console.log(Functor.of(1).map(add4).map(multiply4)) // Functor { val: 20 }
接下来介绍各类常见的函子。
Maybe 函子是指在 map 方法中增长了对空值的判断的函子。
因为函子中的 map 方法中的函数参数会对函子内部的值进行处理,因此当传入函子中的值为空(如 null)时,则可能会产生错误。
function toUpperCase(str) { return str.toUpperCase(); } console.log(Functor.of(null).map(toUpperCase)); // TypeError
Maybe 函子则在 map 方法中增长了对空值的判断,如果函子内部的值为空,则直接返回一个内部值为空的函子。
function Maybe(val){ this.val = val; } Maybe.prototype.map=function(f){ return this.val ? Maybe.of(f(this.val)) : Maybe.of(null); } Maybe.of = function(val) { return new Maybe(val); }
当使用 Maybe 函子时传入空值则不会报错
console.log(Maybe.of(null).map(toUpperCase)); // Maybe { val: null }
Either 函子是指内部有分别有左值(left)和右值(right),正常状况下会使用右值,而当右值不存在的时候会使用左值的函子。
function Either(left,right){ this.left = left; this.right = right; } Either.prototype.map=function(f){ return this.right ? Either.of(this.left, f(this.right)) : Either.of(f(this.left), this.right); } Either.of = function(left,right) { return new Either(left,right); }
以下当左右值都存在的时候则以右值为函子的默认值,当右值不存在是则以左值为函子的默认值。
function addOne(x) { return x+1; } console.log(Either.of(1,2).map(addOne)); // Either { left: 1, right: 3 } console.log(Either.of(3,null).map(addOne)); // Either { left: 4, right: null }
Monad 函子是指可以将函子多层嵌套解除的函子。
咱们往函子传入的值不只仅能够是普通的数据类型,也能够是其它函子,当往函子内部传其它函子的时候,则会出现函子的多层嵌套。以下
var functor = Functor.of(Functor.of(Functor.of('ttsy'))) console.log(functor); // Functor { val: Functor { val: Functor { val: 'ttsy' } } } console.log(functor.val); // Functor { val: Functor { val: 'ttsy' } } console.log(functor.val.val); // Functor { val: 'ttsy' } Monad 函子中新增了 join 和 flatMap 方法,经过 flatMap 咱们可以在每一次传入函子的时候都将嵌套解除。 Monad.prototype.map=function(f){ return Monad.of(f(this.val)) } Monad.prototype.join=function(){ return this.val; } Monad.prototype.flatMap=function(f){ return this.map(f).join(); } Monad.of = function(val) { return new Monad(val); }
经过 Monad 函子,咱们最终获得的都是只有一层的函子。
console.log(Monad.of('ttsy').flatMap(Monad.of).flatMap(Monad.of)); // Monad { val: 'TTSY' }
在咱们平时的开发过程当中,要根据不一样的场景去实现不一样功能的函数,而函数式编程则让咱们从不一样的角度去让咱们可以以最佳的方式去实现函数功能,但函数式编程不是非此即彼的,而是要根据不一样的应用场景去选择不一样的实现方式。
摘自掘金:http://www.javashuo.com/article/p-embzophk-y.html
做者:刘伟波
连接:http://www.liuweibo.cn/p/207
来源:刘伟波博客