JS函数式编程

发布时间 2023-06-27 13:12:48作者: lix_uan

高阶函数

  • 函数作为参数
  • 函数作为返回值
  • 意义:屏蔽细节,抽象通用的问题

函数作为参数

let array = [1, 2, 3, 4]

// forEach
const forEach = (array, fn) => {
  for (let i = 0; i < array.length; i++) {
    fn(array[i])
  }
}

forEach(array, item => console.log(item))

// filter
const filter = (array, fn) => {
  const result = []
  for (let i = 0; i < array.length; i++) {
    if (fn(array[i])) {
      result.push(array[i])
    }
  }
  return result
}

let res = filter(array, item => item % 2 === 0)
console.log(res)

函数作为返回值

// once
const once = fn => {
  let done = false
  return function () {
    if (!done) {
      done = true
      fn.apply(this, arguments)
    }
  }
}

let pay = once(i => console.log(i))
// 只会执行一次
pay(5)
pay(5)
pay(5)

其他常用高阶函数的实现

let array = [1, 2, 3, 4, 5]

// map
const map = (array, fn) => {
  let results = []
  for (const value of array) {
    results.push(fn(value))
  }
  return results
}

const mapTest = map(array, item => item * 2)
console.log(mapTest)

// every
const every = (array, fn) => {
  let result = true
  for (const value of array) {
    result = fn(value)
    if (!result) {
      break
    }
  }
  return result
}

const everyTest = every(array, item => item > 10)
console.log(everyTest)

// some
const some = (array, fn) => {
  let result = false
  for (const value of array) {
    result = fn(value)
    if (result) {
      break
    }
  }
  return result
}

const someTest = some(array, item => item > 10)
console.log(someTest)

闭包

  • 可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员

  • 闭包的本质:函数在执行的时候会放到一个执行栈上当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员

// 生成计算数字的多少次幂的函数
const makePowerFn = power => base => base ** power

const square = makePowerFn(2)
const cube = makePowerFn(3)

console.log(square(2))
console.log(square(3))
console.log(cube(2))
console.log(cube(3))

纯函数

  • 相同的输入永远会得到相同的输出,而且没有任何可观察的副作用
  • 可缓存:因为纯函数对相同的输入始终有相同的结果,所以可以把纯函数的结果缓存起来
  • 可并行处理:纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数 (Web Worker)
// memoize
const memoize = fn => {
  let cache = {}
  return args => {
    if (args in cache) {
      return cache[args]
    } else {
      let result = fn(args)
      cache[args] = result
      return result
    }
  }
}

const getArea = r => {
  console.log('calculating...')
  return Math.PI * r * r
}
let getAreaWithMemory = memoize(getArea)
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))

柯里化

  • 当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变)
  • 然后返回一个新的函数接收剩余的参数,返回结果
const curry = fn => {
  return (curriedFn = (...args) => {
    if (args.length < fn.length) {
      return (...args2) => {
        return curriedFn(...args, ...args2)
      }
    }
    return fn(...args)
  })
}

const getSum = (a, b, c) => {
  return a + b + c
}
curriedGetSum = curry(getSum)
console.log(curriedGetSum(1)(2, 3))

函数组合

  • 如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数
  • 函数组合默认是从右到左执行
const compose =
  (...fns) =>
  val =>
    fns.reduceRight((arg, fn) => fn(arg), val)

const toUpper = s => s.toUpperCase()
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const f = compose(toUpper, first, reverse)
console.log(f(['one', 'two', 'three']))

函子(Functor)

  • 包含值和值的变形关系(这个变形关系就是函数)

  • 是一个特殊的容器,通过一个普通的对象来实现,该对象具有 map 方法,map 方法可以运

    行一个函数对值进行处理(变形关系)

// 一个容器,包裹一个值
class Container {
  // of 静态方法,可以省略 new 关键字创建对象
  static of(value) {
    return new Container(value)
  }

  constructor(value) {
    this._value = value
  }

  // map 方法,传入变形关系,将容器里的每一个值映射到另一个容器
  map(fn) {
    return Container.of(fn(this._value))
  }
}

const res = Container.of(2)
  .map(x => x + 2)
  .map(x => x * 2)

console.log(res._value)

MayBe函子

class MayBe {
  static of(value) {
    return new MayBe(value)
  }
  constructor(value) {
    this._value = value
  }
  map(fn) {
    return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
  }
  isNothing() {
    return this._value === null || this._value === undefined
  }
}

// 在 MayBe 函子中,很难确认是哪一步产生的空值问题
MayBe.of('hello world')
    .map(x => x.toUpperCase())
    .map(x => null)
    .map(x => x.split(' '))
// => MayBe { _value: null 

Either函子

class Err {
  static of(value) {
    return new Err(value)
  }

  constructor(value) {
    this._value = value
  }

  map(fn) {
    return this
  }
}

class Succ {
  static of(value) {
    return new Succ(value)
  }

  constructor(value) {
    this._value = value
  }

  map(fn) {
    return Succ.of(fn(this._value))
  }
}

// 用Either函子来处理异常
const parseJSON = json => {
  try {
    return Succ.of(JSON.parse(json))
  } catch (e) {
    return Err.of({ error: e.message })
  }
}

const r = parseJSON('{"name": "zs"}').map(x => x.name.toUpperCase())
console.log(r)

Monad函子

  • 一个函子如果具有 join 和 of 两个方法并遵守一些定律就是一个 Monad
const fp = require('lodash/fp')
// IO Monad
class IO {
  static of(x) {
    return new IO(function () {
      return x
    })
  }
  constructor(fn) {
    this._value = fn
  }
  map(fn) {
    return new IO(fp.flowRight(fn, this._value))
  }
  join() {
    return this._value()
  }
  flatMap(fn) {
    return this.map(fn).join()
  }
}
let r = readFile('package.json').map(fp.toUpper).flatMap(print).join()