4.函数式编程范式笔记

本文为拉勾网大前端高薪训练营第一期笔记


函数式编程的意义

  • 随着react的流行受到越来越多的关注
  • Vue 3也开始用函数式编程
  • 抛弃this
  • 打包过程可以更好的tree shaking过滤无用代码
  • 方便测试,方便并行处理
  • 很多库可以帮助做函数式开发,lodash、underscore、ramda

MDN first-class function

  • 函数可以存储在变量
  • 函数可以作为参数
  • 函数作为返回值

函数可以存在变量

const BlogController = {

create(content){ return Db.create(content) },

}

等效

const BlogController = {

create: Db.create

}

高阶函数

函数作为返回值

意义:

  • 抽象屏蔽细节,我们只需要关注目标
  • 抽象通用的问题

闭包

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

function makeFn(){
	let msg = 'Hello Function'
	return function() {
		console.log(msg)
	}
}

const fn = makeFn()
fn()

function once(fn) {
  let done = false
  return function (){
    if(!done){
      done = true
      return fn.apply(this, arguments)
    }
  }
}
let pay = once(function (money){
  console.log(`pay: ${money}`)
})

pay(5)
pay(5)

闭包的本质

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

纯函数

相同的输入永远得到相同的输出

纯函数的好处两点

  • 可缓存,由于特性是同输入,输出恒定,可以用记忆函数来加速

例如

const getAreaWithMemory = _.memoize(getArea)
  • 可测试,因为每次输入相同输出应该恒定

副作用:如果有外部变量参与纯函数运算,就会把纯函数变成不纯

函数柯里化currying

把多个参数的函数,变成函数包函数,第一次只传部分不怎么变化的参数,然后第二次传剩下函数。

_.curry()函数柯里化

const sum = _.curry((a,b,c)=>a+b+c)
sum(1,2,3)
sum(1)(2,3)
sum(2,3)(1)

自己实现_.curry()

const curry = (fn) => {
    return function curriedFn(...args){
        if(args.length < fn.length){
            return function(){
                return fn(...args.concat(Array.from(arguments)))
            }
        } else {
            return fn(...args)
        }
    }
}

const sum = curry((a,b,c)=>a+b+c)

console.log(sum(1,2,3))
console.log(sum(1,2)(3))

fn.length能拿到形参的数量

组合函数

_.flow() _.flowRight()后者是从右向左执行

自己实现flowRight

const flowRight = function (...args) {
    return function (value) {
        return args.reverse().reduce((acc, fn)=>{
            fn(acc)
        }, value)
    }
}

//单行写法
const flowRight = (...args) => value=> {return args.reverse.reduce(()=>{fn(acc)}, value)}

函数组合符合结合律

compose(f,g,h)等效compose(compose(f,g), h)等效compose(f, compose(g,h))

函数组合的注意

函数组合传入的都需要是一元函数,如果是二元或者更多就需要柯里化变成一元,调试的时候可以加log在函数组合之中

const log = v => {
	console.log(v)
	return v
}

//tag版本的log
const trace = _.curry((tag, v) => {
	console.log(tag, v)
	return v
})

trace('tag')

lodash fp

const fp = require(‘lodash/fp’)

fp.map(_.toLower)(array)

自带柯里化和参数的函数优先,数值后置

fp.map只会把数据传到下一个函数里,不会带上index collection

Point Free

  • 不需要指明处理的数据
  • 只需要合成运算过程
  • 需要定义一些辅助的基本运算函数

Functor(函子)

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

函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)

我的理解为用容器包数值,然后通过map传运算函数,最后返回新的容器,不断重复这个过程

maybe函子是处理了null和undefined的情况

either函子是处理了有错误的情况,可以把错误传到最后

IO函子是返回处理的function,不传值,最后运行的时候才生成值,函子部分永远是纯的,因为返回的是运算本身,不纯的操作甩锅到最后执行

folktale库可以有compose curry,也制造task函子,使用起来像spark

const { compose, curry } = require('folktale/core/lambda')
// Task 处理异步任务
const fs = require('fs')
const { task } = require('folktale/concurrency/task')
const { split, find } = require('lodash/fp')

function readFile (filename) {
  return task(resolver => {
    fs.readFile(filename, 'utf-8', (err, data) => {
      if (err) resolver.reject(err)

      resolver.resolve(data)
    })
  })
}

readFile('package.json')
  .map(split('\n'))
  .map(find(x => x.includes('version')))
  .run()
  .listen({
    onRejected: err => {
      console.log(err)
    },
    onResolved: value => {
      console.log(value)
    }
  })

pointed函子是实现了of静态方法的函子

避免使用new创建函子,更深层的含义是of方法用来把值放在上下文Context(把值放在函子,用map处理值)

Monad函子就是变扁的Pointed函子,一个函子如果同时具有join和of两个方法并遵守一些定律就是一个Monad

我的理解是当一个函子返回一个函子时,就flatMap,解决函子嵌套的问题,flatMap里就是把map合并操作和返回函子内部值join连续操作,如果普通的fp.toUpper这样的函数就直接用map就好了

错题分析

函数式编程不能提高性能

副作用也就是外界参数参与纯函数,使得纯函数变得不纯,不可避免


本文为拉勾网大前端高薪训练营第一期笔记

你可能感兴趣的:(函数式编程,前端)