学习疑问
-
箭头函数是为了函数式编程而出现的吗?
-
学习下浏览器调试工具、console日志方法。
学习心得
一、闭包
函数在执行时会创建一个local作用域,一般函数执行完后这个作用域会被移除,但如果被外部引用后就不能释2.放了,当调用这个函数时闭包就会做为一个作用域链被访问。
解除闭包的方式是将引用置为null。
二、柯里化
函数式编程的一个好处就是可以让我们最大程度的去重用我们的函数。
实现柯里化:模拟lodash的柯里化,原来定义一个函数,我们可以通过它的length属性来知道它接收几个参数。如下为源码,这里面使用了很多闭包,通过闭包保存了入参函数、函数参数及其数量。这里需要自己去敲一下。好好理解一下实参小于形参时,为什么要把 curriedFn return回去。
function curry(func) {
return function curriedFn(...args) {
// 判断实参和形参的个数
if (args.length < func.length) {
return function () {
return curriedFn(...args.concat(Array.from(arguments)))
}
}
return func(...args)
}
}
三、函数式编程
1.函数组合
在redux中间件中就有使用到函数组合,其源码使用了reduce,改变了函数组合从右到左的执行顺序,lodash中也有一样的方法。
可以把多个函数组合成新的函数,调用这个函数,会从第一个函数开始调用,并将中间结果一层层传递,交给下一个函数处理,当最终函数执行完毕会将结果返回。
记录4种 lodash.flowRight 方法中 compose 函数实现方式。
-
- 写法一:redux中间件官方写法
function compose(...funcs) {
if (funcs.length === 0) {
// 如果没有要组合的函数,则返回的函数原封不动的返回参数
return args => args;
} else if (funcs.length === 1) {
// 要组合的函数只有一个
return funcs[0];
}
// 无比简洁
return funcs.reduce((acc, fn) => (...args) => acc(fn(...args)))
}
-
- 写法二:通过变量记录返回值
function compose(...funcs) {
if (funcs.length === 0) {
return args => args;
} else if (funcs.length === 1) {
return funcs[0];
}
// 这是可读性好一些的写法,和上面代码功能一样
return function (...args) {
let lastReturn = null; // 记录上一个函数返回的值
for (let i = funcs.length - 1; i >= 0; i--) {
const func = funcs[i];
if (i === funcs.length - 1) {
lastReturn = func(...args);
} else {
lastReturn = func(lastReturn);
}
}
return lastReturn;
}
}
-
- 写法三:lodash官方写法,和方法二类似,只是换成了while实现,也是通过一个变量记录执行位置,并且保存返回值。
function compose(...funcs) {
const length = funcs.length
// 倒转数组,变为flowRight
funcs.reverse();
if (length === 0) {
return args => args;
} else if (length === 1) {
return funcs[0];
}
return function (...args) {
let index = 0
let result = length ? funcs[index].apply(this, args) : args[0]
while (++index < length) {
result = funcs[index].call(this, result)
}
return result
}
}
-
- 写法四:和写法一类似,但是是将函数参数,直接传入,这个写法是有问题的,如果有多个参数,就会出问题。改良写法在下面,但还是有些问题。
function compose(...funcs) {
const length = funcs.length
// 倒转数组,变为flowRight
if (length === 0) {
return args => args;
} else if (length === 1) {
return funcs[0];
}
return value => funcs.reverse().reduce((acc, fn) => fn(acc), value)
// 改良写法
return (...args) => funcs.reverse().reduce((acc, fn) => fn(...acc), args);
}
// 可浓缩为以下代码
// const compose = (...args) => value => args.reverse().reduce((acc, fn) => fn(acc), value)
- 函数组合的调试
平常写代码,在处理数据时,就是将数据通过不同的API方法处理后得到想要的结构,但是从未以这样的函数式思想方式,以函数管道,柯里化的方式将函数组合起来,形成功能强大的函数去使用。
2.lodash-fp模块
lodash当中的map方法和fp模块下的map方法,是有区别的。前者的迭代函数会传入value、index|key、collection三个参数,而后者只会传入value一个参数。在通过map处理parseInt方法时,能明显看出这一点。
3.Point Free思想
它是一种编程风格,将一个大的功能先解构抽象成很多小功能函数,再使用函数组合方式合并起来,方便后面调用。核心是抽象、合并。
4.函子
一个函子一般具备一个静态of方法、原型map方法和构造函数方法,它形成了一种链式调用的方式。而且这个链式调用似乎不用关心this指向的问题,只根据传参得到不同的结果。
constructor函数将外界传入的参数保存了下来,并且不对外公布。而外界想要处理它只能使用map方法,并且该方法会返回一个函子,所以能进行链式调用。of方法是替代了new的使用方式。
promise应该就是采用了这种思想。
函子的使用过程中还会遇到很多错误,所以我们还需要一些别的函子来增强功能。
class Container {
static of (value) {
return new Container(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return Container.of(fn(this._value))
}
}
MayBe 函子
通过Maybe函子控制副作用,其实就是增加了一个isNothing方法去判断是否传入空值,来进行不同处理。
isNothing() {
return this._value === null || this._value === undefined
}
但它也存在问题,虽然它能处理空值问题,但是多次调用map方法,并不能知道是哪次出现了空值,所以还需要新的函子来解决这个问题。
Either 函子
它分为Left、Right两个函子,区别是map方法返回不同的东西。Left会直接返回传入的参数,不作任何处理,所以它被用来记录错误信息。Right则是正常执行。核心是在封装方法时要使用try...catch,在内部使用Left、Right来处理逻辑。
IO 函子
IO函子和其它函子主要不同的是of和map方法,of一般是接收一个可能有副作用的函数储存起来,而map会将接收的函数都组装起来,再传回给使用者,让使用者去调用这个方法去处理副作用。就是不论有没有副作用操作,都将它延迟到了调用的时候。
Task 函子
folktale也是一个函数式编程库,里面有个task函子,可以解决异步编程问题。其使用方法类似于promise,它额外具有run、listen等静态方法。
Pointed 函子
这个应该放在第一个讲,因为我们每个函子都用到了它。它就是我们的of方法,它更是一种思想,它将值放入到上下文中,在上下文中去处理我们的值。这个上下文就是我们的函子,每一次链式调用map都创建了一个函子,里面的值互不冲突干涉。
Monad 函子(单子)
它解决了函子嵌套问题,普通的IO函子有个问题,当嵌套使用IO函子时,代码风格会很差需要链式调用_value方法。Monad就是解决函子嵌套的问题,它多了join、flatMap方法。
join是直接返回_value的调用结果,课程代码中没针对_value不同类型做处理,默认为函数。flatMap是同时调用map和join方法,最终将_value执行的结果返回了出来。当想合并一个函数,并且这个函数返回一个值,就使用map方法;返回的是函子,则使用flatMap方法。
这里的map方法作用是将函数组合起来,返回一个IO函子,_value存储组合好的函数,而不是像其它函子,返回的是值。flapMap方法,意思是抹平map方法,作用就是调用_value储藏的函数拿到结果,并返回回去。它就是先调用map方法,将fn方法和之前的_value中方法组合起来,又得到一个IO函子,再调用join方法,我们就拿到了新组合好的方法本身,等于是少了一层嵌套,这个_value既代表了是个组合函数,同时它也可看作是一个值得传递。这里有些绕,可能过段时间就模糊淡忘原理了,所以先只记好如何使用。
函子小结
函子基本都是一个有_value属性的对象,通过of接收值,map接收函数来操作值。map方法中传递的函数都应该是柯里化后的函数,使用方便。
总结
函数式编程核心思想是把运算过程抽象成函数,编码时面向函数去编程。由此衍生出了柯里化、函数组合、函子等思想。