写在前面
笔记内容大多出自于拉勾教育大前端高薪训练营的教程,因此也许会和其他文章雷同较多,请担待。
函数式编程(Functional Programming, FP)
- 是一种编程范式,POP(面向过程编程)、OOP(面向对象编程)也是编程范式的一种
- 简而言之就是将参数抽象定义到函数体内,并且将过程封装到函数中,达到I/O的映射关系(前提该函数为纯函数)
函数是一等公民(First-class Function)
- 函数是一个对象,所以可以存储到变量/数组中,也可以作为另一个函数的返回值
- 以下两种写法其实是一致的,因为传参相同,所以可以直接将需要返回的函数赋值给该函数
const Controller = {
index(post) {
return Service.index(post)
},
list(post) {
return Service.list(post)
}
}
const Constroller = {
index: Service.index,
list: Service.list
}
高阶函数(Higher-order Function)
函数作为参数
function forEach(array, fn) {
for (let i = 0; i < array.length; i++) {
fn(array[i])
}
}
function filter(array, fn) {
let results = []
for (let i = 0; i < array.length; i++) {
fn(array[i]) && results.push(array[i])
}
return results
}
function map(array, fn) {
let results = []
for (let i = 0; i < array.length; i++) {
results.push(fn(array[i]))
}
return results
}
function every(array, fn) {
let result = true
for (let i = 0; i < array.length; i++) {
result = fn(array[i])
if (!result) break
}
return result
}
function some(array, fn) {
let result = false
for (let i = 0; i < array.length; i++) {
result = fn(array[i])
if (result) break
}
return result
}
函数作为返回值
function once(fn) {
let flag = false
return function() {
if (!flag) {
flag = true
return fn.call(this, ...arguments)
}
}
}
纯函数
- 相同的输入会得到相同的输出,且原函数不会在调用后被改变
- 因为slice输入相同得到了相同的答案,所以slice是纯函数,splice则为不纯的函数
const array = [1, 2, 3, 4, 5]
array.slice(0, 1) -> [1]
array.slice(0, 1) -> [1]
array.slice(0, 1) -> [1]
array.splice(0, 1) -> [1]
array.splice(0, 1) -> [2]
array.splice(0, 1) -> [3]
因为结果相同,所以可以创建一个cache来记录返回值
function memoize(fn) {
let cache = {}
return function() {
const args = JSON.stringify(arguments)
cache[args] = cache[args] || fn.call(this, ...arguments)
return cache[args]
}
}
闭包
- 可以在另一个作用域中调用一个函数的内部函数,达到可以访问该函数作用域内的成员的作用
- 闭包被执行时,父函数会从执行栈上被释放,但是子函数内引用的父函数的成员是不会被释放的,即使子函数被执行完以后也不会释放,因为引用的父函数的成员不属于自己的作用域内,没有垃圾回收的权限,所以要注意使用闭包时要多去考虑内存溢出相关事宜,在使用过后要手动释放那些被引用却未被释放的父函数的成员
const power = power => number => Math.pow(number, power)
const power2 = power(2)
柯里化
先上一段来源于Vue源码的柯里化类型判断
// utils.js
const is = type => obj => Object.prototype.toString.call(obj) === `[object ${type}]`
const isObject = is('Object')
const isArray = is('Array')
const isFunction = is('Function')
柯里化其实就是运用了闭包的思想,将父函数的arguments存放在子函数中,在需要的时候进行执行
已知长度的柯里化实现
function curry(fn) {
return function inner(...args) {
if (fn.length == args.length) {
return fn.call(this, ...args)
} else {
return function() {
return inner(...args.concat(Array.from(arguments)))
}
}
}
}
const add = curry((a, b, c) => a + b + c)
console.log(add(1, 2, 3)) // 6
console.log(add(1)(2)(3)) // 6
console.log(add(1)(2, 3)) // 6
未知长度的柯里化实现
function curry(fn) {
return function inner(...args) {
return function(...arguments) {
if (arguments.length) {
return inner(...args.concat(arguments))
} else {
return fn.call(this, ...args)
}
}
}
}
const add = curry(function(...args) {
return args.reduce((prev, curv) => {
return prev + curv
}, 0)
})
console.log(add(1, 2)()) // 3
console.log(add(1)(2)(3)()) // 6
console.log(add(1)(2, 3)(4)()) // 10
lodash
flowRight -> compose 函数组合
- 符合结合律
const toUpper = str => str.toUpperCase()
const reverse = array => array.reverse()
const first = array => array[0]
const log = result => {
console.log(result)
return result
}
const compose = (...args) => {
args = args.reverse()
return value => args.reduce((prev, curv) => curv(prev), value)
}
const upperLastEle = compose(compose(toUpper, log, first), log, reverse)
console.log(upperLastEle(['a', 'b', 'c'])) // ['c', 'b', 'a'] c C
console.log(upperLastEle(['e', 'f', 'g'])) // ['g', 'f', 'e'] g G
Functor函子
- 容器:包含值和值的变形关系(这个变形关系就是函数)
- 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)
- 函数式编程的运算不直接操作值,而是由函子完成
- 函子就是一个实现了map契约的对象,想要处理值时就向map方法中传递一个处理值的纯函数
- 最终返回的结果也是一个包含了被处理完成了的值的函子
class Container {
constructor(value) {
this._value = value
}
map(fn) {
return new Container(fn(this._value))
}
}
// Container { _value: 36 }
new Container(5).map(x => x + 1).map(x => x * x)
/* ------- 改造 ------- */
class Container {
static of(value) {
return new Container(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return Container.of(fn(this._value))
}
}
// Container { _value: 36 }
Container.of(5).map(x => x + 1).map(x => x * x)
Maybe函子
- 编程过程中会出现很多错误,需要对错误进行处理
- Maybe函子的作用就是对外部传入空值时进行处理(控制副作用在允许的范围内)
class Maybe {
static of(value) {
return new Maybe(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return this.isEmpty() ? Maybe.of(null) : Maybe.of(fn(this._value))
}
isEmpty() {
return this._value === null || this._value === undefined
}
}
Either函子
- 类似if…else…的处理
- 异常会让函数变得不纯,Either函子可以用来进行异常处理
class Left {
static of(value) {
return new Left(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return this
}
}
class Right {
static of(value) {
return new Right(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return Right.of(fn(this._value))
}
}
const parseJSON = (str) => {
try {
return Right.of(JSON.parse(str))
} catch(e) {
return Left.of({ error: e.message })
}
}
// Left { _value: { error: 'Unexpected token n in JSON at position 2' } }
parseJSON('{ name: aeorus }')
// Left { _value: { error: 'Unexpected token n in JSON at position 2' } } 因为map返回了自身this,并未对函数做任何变动
parseJSON('{ name: aeorus }').map(x => x.name.toUpperCase())
// Right { _value: { name: 'aeorus' } }
parseJSON('{ "name": "aeorus" }')
// Right { _value: { name: 'AEORUS' } }
parseJSON('{ "name": "aeorus" }').map(x => x.name.toUpperCase())
IO函子
- IO函子中的_value是一个函数,将函数当做一个值来处理
- IO函子可以将不纯的操作存储到_value中,延迟执行这个不纯的操作(惰性执行)
- 将不纯的操作交由调用者来处理
class IO {
static of(value) {
return new IO(() => value)
}
constructor(fn) {
this._value = fn
}
map(fn) {
return new IO(_.flowRight(fn, this._value))
}
}
class Console {
log(message) {
return message
}
}
const r = IO.of(Console).map(p => p.log('IO'))
// Promise { <fulfilled>: "IO" }
r._value()