前端知识点——函数式编程

写在前面

笔记内容大多出自于拉勾教育大前端高薪训练营的教程,因此也许会和其他文章雷同较多,请担待。

函数式编程(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()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值