入门函数式编程
在上了360的前端星计划课程后,被月影老师提出的高阶函数思想给深深吸引了,上网查了一下,好像现在正是函数式编程流行大爆发的时候,那还有什么借口不去学习呢。
个人理解
高阶函数满足的条件:函数作为参数来传递,函数作为返回值来输出。也就是处理函数并且返回函数的函数,大概长成这个样子的:
function highLevel (fn) {
return function (...rest) {
// 对fn做一些事
};
}
而高阶函数是函数式编程的编程范式。
经典例子
其实在我之前不知道高阶函数这个名词的时候,我就已经见过它了,因为我见过几个经典的demo。
单例模式
function getSingle (fn) { let result; return function (...rest) { return result || result = fn.apply(this, rest); } }
节流
function throttle (fn, time) { let timer; return function (...rest) { if (!timer) { timer = setTimeout(() => { fn.apply(this, rest); timer = null; }, time); } }; }
防抖
function debounce (fn, time) { let timer; return function (...rest) { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, rest); clearTimeout(timer); }, time); }; }
分时
function timeChunk (arr, fn, count, time) { let timer; let deal = function () { for (let i = 0; i < Math.min(count, arr.length); i++) { fn(arr.shift()); } } return function () { timer = setInterval(() => { if (arr.length === 0) { return clearInterval(timer); } deal(); }, time); }; }
函数式编程的基础
概念基础
函数是一等公民
意思就是函数和其他基本类型变量一样,本来嘛,函数也是对象,而对象是JavaScript的基本类型之一。所以函数是一等公民。
既然函数是一等公民,那么能直接调用就直接,不用做无意义的嵌套。
// 比如 function super (callback) { return sub (function (a) { return callback(a); }); } // 直接 sub(callback);
纯函数
纯函数是没有任何副作用的函数,对同样的输入只会有同样的输出。不受外界影响,也不会干扰外界(意思是纯函数除了参数是由外部决定之外,内部是不受外部变量影响的,且纯函数不会改变参数本身),严格来说就是不与函数外部环境发生任何交互。比如操作dom,log打印之类的都是属于不纯的。
为什么要纯函数?
- 可测试性: 纯函数可以很容易的进行单元测试,因为不需要构造其特定的依赖环境。
- 可缓存性:因为纯函数只要参数不变,其输出结果一定不变,所以可以缓存。
基本方法
柯里化
在函数式编程中柯里化是必不可少的,柯里化的作用是将多参函数分解成单参函数。
对柯里化的好处,目前我大概理解的是单参函数方便函数的复用和函数之间的组合。
上代码:
let curry = function (fn) { let len = fn.length, args = []; return function curried (...rest) { args = args.concat(rest); if (len > args.length) { return curried; } return fn.apply(this, args); } }; let testFn = function (a, b, c) { return a + b + c; } curry(testFn)(1, 2, 3); // 6; curry(testFn)(1, 2)(3); // 6; curry(testFn)(1)(2)(3); // 6;
其实很简单,就是先将参数缓存着,然后够了后就开始执行,有点代理模式中缓存代理的感觉。
函数组合
利用函数是一等公民和纯函数的特点,函数式编程中很有意思的就是函数组合。
函数组合就是将各各纯函数以管道形式组合起来,类似一个pipe,来个例子:
let toUpperCase = function (str) { return str.toUpperCase(); } let reverseStr = function (str) { return str.split('').reverse().join(''); } // 组合函数 let compose = function (fn, gn) { return function (...rest) { return fn(gn.apply(this, rest)); }; } let getNewStr = compose(reverseStr, toUpperCase); getNewStr('hello'); //OLLEH
这里的compose组合方式参考Ramda.js,从右到左执行循序,然后给组合函数提供一个参数'hello',然后参数通过组合函数中的每一个函数处理后输出结果。当然这里的
compose
只能处理两个函数,所以可以稍微扩展一下:let compose = function (...rest) { let fns = rest, len = rest.length; return function () { for (let i = len - 1; i >= 0; i--) { let fn = fns[i], args = Array.prototype.slice.call(arguments, 0, fn.length || arguments.length), nextArgs = []; nextArgs.push(fn.apply(this, args)); arguments = nextArgs; } return arguments[0]; }; };
这样
compose
就可以处理多个函数了,可是compose的运行规则是,除了最后一个函数是接收外界参数以外,里面的每一个参数都是接收上一个函数的返回值。那如果compose参数里面想要放置多参函数怎么办呢,柯里化就来起作用了。比如:const testArr = ['qq', 'qwq', 'www', 'js']; let hasQ = function (str) { return /q/i.test(str); }; let filter = function (fn, arr) { return arr.filter(fn); }; let map = function (fn, arr) { return arr.map(fn); }; let getHead = function (arr) { return arr[0]; }; let filterQ = curry(filter)(hasQ); let mapUpperCase = curry(map)(toUpperCase); let getResult = compose(getHead, mapUpperCase, filterQ); getResult(testArr); //QQ
上面的程序中,多参函数filter和map均被柯里化后就可以放入compose函数中运行。其实这两个多参函数还有一个特点,那就是参数中函数在前,要处理的数据在后,这样也是很理所当然的,想想看,我们使用柯里化固定的是前面的参数,固定的参数只可能是计算方法呀,哪有固定数据的,将数据参数固定后还能干什么呢。(当然也可以柯里化后面的参数,比如说在奇舞周刊里面有一期提到的bindRight,但是毕竟习惯是固定前面的参数)
在阮一峰的博客中也说过这一点,在博客里阮一峰大大极大的推崇了这种理念:"function first,data last",也就是Ramda.js所坚持的,然后提出了pointfree的概念,即函数组合只组合纯粹的计算过程。
关于函数式编程,还有一个范畴的概念,我没有很好地理解明白,感觉就是一个映射,姑且先这么理解着,以后有了新的理解再来补充。
如果这篇文章对你很有帮助,你可以犒劳一下WO
打赏