函数式编程入门已经看完了,来总结一下吧。学了大概半个多月了,觉得学到了很多东西,想应用到实际中去,于是面试的时候问了一下面试官函数式编程在实际开发中的应用场景。面试官说函数式编程是个好东西,但是实际应用的场景不多。而且用函数式编程写出来的东西没接触过的人可能看不懂,需要通读代码。不过多了解总是好的。还是有点失望的。但是总是有机会用到的,就算只是比别人多了解一点也是好的。以下代码复杂一点的我会写 ES6 和 ES5 两个版本,看的清楚一些。简单的就只写 ES6。话不多说,总结一下这段时间的学习。
第一章 函数式编程简介
第一章简要的介绍了一下函数式编程,以及函数式编程可以带来的一些好处。比较重要的有下面几个方面
- 函数式编程仅依赖输入输出就可以完成自身的逻辑,不会改变任何外部变量的值。
- 函数的引用透明性:对于所有相同的输入都返回相同的输出。
- 函数式编程是告诉代码该怎么做,声明式编程只关心做什么
具体的可以参考
第二章 JavaScript 函数基础
这一章主要讲了一些 js 里面的基本概念,如何用 babel 将 ES6 的代码转换成 ES5 的。然后构建了我们将要运行的代码的环境
具体的可以参考
第三章 高阶函数
这一章主要讲了一下高阶函数的概念,即接受另一个函数作为参数的函数称为高阶函数。然后简单介绍了 js 中的函数。并介绍了高阶函数与抽象的关系。抽象让我们能够专注于预定的目标而无需关心底层的实现。我们还创建了几个简单的函数来帮助理解抽象的概念。
具体的可以参考
第四章 闭包与高阶函数
第四章简单介绍了闭包的概念,然后介绍了闭包在高阶函数中的应用并创建了几个简单的函数
-
tap 函数
tap 函数接收一个参数,返回一个包含该参数的闭包函数。主要用于调试。
// ES6const tap = (value) => { (fn) => { typeof(fn) === 'function' && fn(value) console.log(value) }}// ES5var tap = function(value){ return function(fn){ typeof(fn) === 'function' && fn(value) console.log(value) }}复制代码
-
unary 函数
接收一个函数,将它的参数长度变为 1。比如 parseInt 接收两个参数,因此使用 map 方法会发生错误。这时候就是它的用武之地了
const unary = (fn) => { return fn.length === 1 ? fn : arg => fn(arg)}复制代码
-
once 函数
接收一个函数。顾名思义,就是该函数只能运行一次,比如支付等场景下
// ES6const once = (fn) => { let done = false; return function(){ return done ? undefined :((done = true),fn.apply(this,arguments)); }}复制代码
-
memoized 函数
接收一个函数,返回一个函数,每次调用的结果先在 cache 中找,如果找不到再调用原函数。
// ES6const memoized = (fn) => { const cache = {} return (arg) => cache[arg] || (cache[arg] = fn(arg))}// ES5var memoized = function(fn){ var cache = {} return function(arg){ return cache[arg] || (cache[arg] = fn(arg)) }}复制代码
具体可以参考
第五章 数组的函数式编程
首先我们介绍了投影的概念,即传入值并返回值的函数称为投影函数,比如 map。然后我们简单实现了 map,filter,concatAll,reduce 和 zip 函数
-
map 函数
const map = (array, fn) => { let results = []; for(let value of array){ results.push(fn(value)) } return results}复制代码
-
filter 函数
const filter = (array, fn) => { let results = []; for(let value of array){ fn(value) ? results.push(fn(value)) : undefined } return results}复制代码
-
concatAll 函数
concatAll 函数主要用于数组的扁平化
const concatAll = (array,fn) => { let results = [] for(let value of array){ results.push.apply(results,value); } return results}复制代码
-
reduce 函数
// 值默初始值认为 0 的实现const reduce = (array,fn) => { let accumlator = 0; for(let value of array){ accumlator = fn(accumlator,value) } return accumlator}// 带初始值实现const reduce = (array,fn,initial) => { let accumlator; if(initial != undefined){ accumlator = initial; }else{ accumlator = array[0]; } if(initial === undefined){ for(let i = 1; i < array.length; i++){ accumlator = fn(accumlator,array[i]) } }else{ for(const value of array){ accumlator = fn(accumlator,value) } } return [accumlator];}复制代码
-
zip 函数
zip 函数主要是用于合并两个给定的数组。如
{ id:1,name:'zhangsan' } {id:1,age:'20'}
,合并成{ id:1,name:'zhangsan',age:'20'}
。它接受三个参数,前两个分别是两个待合并的数组。最后一个是作用于这两个数组的函数。const zip = (leftArr,rightArr,fn) => { let index,results = []; for(index = 0;index < Math.min(leftArr.length,rightArr.length); index++){ results.push(fn(leftArr[index],rightArr[index])); } return results;}复制代码
具体可以参考
第六章 柯里化与偏应用
本章中我们了解了柯里化与偏应用的基本概念,并实现了柯里化与偏函数。它们主要是用来减少函数的参数的。
-
柯里化
柯里化是一个把多参数函数转换为一个嵌套的一元函数的过程。具体的实现过程如下
const curry = function(fn){ return function curried(...args){ if(args.length < fn.length){ return function(){ return curried.apply(null,args.concat(arguments)); } }else{ return fn.apply(null,args) } }}复制代码
-
偏应用
偏应用是为了弥补柯里化不足的地方产生的。柯里化的参数只能从前往后传,而偏函数的参数可以空出任意位置的参数。具体实现如下
const partial = (fn,...partialArgs){ let args = partialArgs return (...fullArgs) => { let arg = 0; for(let i = 0; i < args.length && i < fullArgs.length; i++){ if(args[i] === undefined){ args[i] = fullArgs[arg++] } } return fn.apply(null,...args); }}复制代码
具体可以参考
第七章 组合与管道
这一章我们由 unix 系统的管道符引出函数式编程中的组合与管道。并且了解到了函数式编程遵循结合律。因此我们可以在 compose 和 pipe 函数中任意组合函数,并且最后得到的结果都是一样的。这就能让我们组合出无数种函数。这一节最重要的是 compose 和 pipe 函数。
-
compose 函数
compose 函数主要是用于将多个函数组合成一个函数
const compose = (fns) => { return function(value){ let fnsCopy = fns.concat(); // 还记得我们发现的 bug 吗 fnsCopy.push(value); return fnsCopy.reverse().reduce((acc,fn)=>{ return fn(acc); }) }}复制代码
-
pipe 函数
pipe 函数其实和 compose 函数一样,只是方向的区别。compose 函数是从后往前,pipe 函数是从前往后,仅此而已。
const pipe = (fns) => { return function(value){ let fnsCopy = fns.concat(); fnsCopy.unshift(value); return fnsCopy.reduce((acc,fn)=>{ return fn(acc); }) }}复制代码
具体可以参考
第八章 函子
这一章我们从如何使用函数式编程的思想处理异常开始,引出了函子的概念。函子其实就是一个普通的对象,但是它实现了 map 方法,在遍历每个值的时候会返回一个新的值。并且如果函子带有 of 方法的话就是一个 pointed 函子(例如 ES6 数组新增了 Array.of 方法,且具有 map 方法,所以它本身就是一个 pointed 函子)。然后我们介绍了两个重要的函子 MayBe 函子和 Either 函子,并以掘金的文章为例展示了函子处理的过程。
-
普通函子
const Container = function(value){ this.value = value;}// 这会使它变成一个 pointed 函子Container.of = function(value){ return new Container(value);}// 实现它的 map 方法Container.prototype.map = function(fn){ return Container.of(fn(this.value))}复制代码
-
MayBe 函子
MayBe 函子可以自动捕捉数据中的 null 和 undefined,从而不会导致程序因为这两个值而崩溃。我们在执行传入函数的时候先对数据做了判断,从而避免了错误。将对错误的处理抽象了出来。
并且 MayBe 函子可以像 Promise.then 那样嵌套使用
const MayBe = function(val){ this.val = val}MayBe.of = function(val){ return new MayBe(val)}MayBe.prototype.isNothing = function(){ return (this.val === null || this.value === undefined)}MayBe.prototype.map = function(fn){ return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this.val));}复制代码
-
Either 函子
虽然 MayBe 函子能避免出现问题,但是并不能告诉我们是哪一个环节出现了问题。而 Either 就可以帮我们解决这个问题。
const Either = { Some: Some, Nothing: Nothing}const Nothing = function(val){ this.value = val;}Nothing.of = function(val){ return new Nothing(val)}Nothing.prototype.map = function(f){ return this;}const Some = function(val){ this.value = val;}Some.of = function(val){ return new Some(val);}Some.prototype.map = function(fn){ return Some.of(fn(this.value))}复制代码
具体可以参考
结语
至此这本书就结束了。通过这本书对函数式编程有了基本的了解,很多东西之前根本没有见过,没有想过竟然还能这么用。感觉对自己的帮助挺大的。虽然这本书看完了,但是对于函数式编程的学习远远没有结束。今天回顾的时候好多已经记不太清楚了。所以还是需要过段时间复习一下,重要的函数要强迫自己记一下。接下来准备把自己这段时间的秋招经历总结一下,虽然没有拿到什么腾讯阿里之类的 offer,但是人要善于总结不是吗?对自己,对别人都是一种帮助。