学习笔记(一)—— 函数式编程

最近开始对前端知识体系进行一个系统的扩展跟学习,通过每天定期的学习,对一些不常使用的知识点进行了解和补充,同时对已经使用过的知识点温故而知新
在此记录学习笔记,并根据学习进度定时跟新

函数一等公民 (First-class Function)

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

高阶函数(Higher-order Function)

  • 可以把函数作为参数传递给另一个函数
  • 可以把函数作为另一个函数的返回结果

常用的高阶函数及模拟实现

  • forEach
// forEach
const forEach = (array, fn) => {
    for (let t of array) {
        fn(t)
    }
}
  • filter
// filter
const filter = (array, fn) => {
    let result = []
    for (let t of array) {
        fn(t) && result.push(t)
    }
    return result;
}
  • map
// map
const map = (array, fn) => {
    let result = []
    for (let t of array) {
        result.push(fn(t))
    }
    return result
}
  • some
// some
const some = (array, fn) => {
    for (let t of array) {
        if(fn(t)) {
            return true
        }
    }
    return false
}
  • every
// every
const every = (array, fn) => {
    for (let t of array) {
        if(!fn(t)) {
            return false
        }
    }
    return true
}
  • once
// once
const once = (fn) => {
    let done = false
    return (...args) => {
        if (!done) {
            done = true
            return fn.apply(this, args)
        }
    }
}

闭包 (Closure)

  • 函数及其周围的状态的引用捆绑在一起,组成闭包
    * 可以在另一个作用域中调用一个函数的内部函数,并访问到该函数的作用域中的成员

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

  • 箭头函数,闭包中this指向undefined,因为外层函数调用栈被释放?

纯函数

  • 相同的输入永远得到相同的输出,且没有任何可观察的副作用
  • 数组的slice和splice分别是纯函数和不纯函数
    * slice返回数组指定部分,不会改变原数组
    * splice从原数组中移除的元素,并返回移除元素集合,会改变原数组
  • 函数式编程不会保留计算中间的结果,所以变量是不可变的(无状态的)

Lodash

具体参见Lodash官网

纯函数的好处

  • 可缓存

    • 模拟实现lodash memoize
    function memoize (fn) {
        let cache = {}
        return function () {
            const key = JSON.stringify(arguments)
            cache[key] = cache[key] || fn(...arguments)
            return cache[key]        
        }
    }
    
  • 可测试
  • 并行处理
  • 纯函数不需要访问共享内存数据

副作用

  • 使纯函数变得不纯
  • 副作用来源:外部数据依赖(接口、数据库、配置文件、全局变量等)
  • 副作用不可能完全禁止,尽可能控制在可控范围内

柯里化(Currying)

  • 当一个函数包含多个参数的时候先传递部分参数去调用他(这部分参数以后永远不变)
  • 然后返回一个新的函数接收剩余参数,并返回结果
  • 柯里化模拟实现
// curry
function curry (fn) {
    return function (...args) {
        if (args.length < fn.length) {
            return function() {
                return fn(...args, ...arguments)
            }
        }
        return fn(...args)
    }
}
  • 柯里化总结
    • 柯里化可以使我们给一个函数传递较少的参数,生成一个记住了某些参数的新函数
    • 是一种对函数的缓存
    • 使函数的粒度更小
    • 可以将多元(多参数)函数转化为一元(单参数)函数

函数组合(compose)

  • 函数组合可以把细粒度的函数重新组合成新的函数

  • 如果一个函数需要多个函数处理才能得到最终值,我们可以将中间过程的函数合并成一个函数

  • 函数组合默认从右向左执行

  • compose函数模拟实现

    // compose
    const compose = (...fns) => (...args) => fns.reduceRight((fn1, fn2) => ()=> fn2(fn1(...args)))()
    
  • lodash中的组合函数

    • flow() 从左向右组合执行
    • flowRight() 从右向左组合执行
  • lodash/fp模块

    • 提供了对函数式编程友好的方法
    • fp模块中提供的方法(例如:map、filter、split等)都是被柯里化的
    • 多个参数的方法函数优先,数据滞后(默认为数据优先、函数滞后,例如map(array, func))
  • lodash-map方法的小问题:parseInt参数问题

    • map(array, func(item, index, array))
    • parseInt(string, radix)
      • string 待parse字符串
      • radix 进制
    • map传递给parseInt 3个参数,将index当成了radix,由此引发问题
    • fp.map柯里化后的func只接收一个参数,因此没有此问题
  • 函数组合只能组合单参数函数

  • PointFree

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

函子(Functor)

  • 容器:包含值和值的变形关系(变形关系即函数)

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

  • 函子用来处理副作用、异步操作、异常处理等

  • 函子对象class实现

    class Functor {
        constructor(value) {
            this._value = value
        }
        static of(value) {
            return new this.prototype.constructor(value)
        }
        map(fn) {
            return this.of(fn(this._value))
        }
    }
    
  • 总结

    • 函数式编程的运算不直接操作值,而由函子完成
    • 函子就是一个实现了map契约的对象
    • 函子中封装了一个值,想要操作值,就给map传递一个处理值的函数(纯函数)
    • 最终返回一个新的函子
  • 函子介绍

    • MayBe函子
      • 用于处理空值(null、undefined)
    class MayBe extends Functor {
        isNothing() {
            return this._value === null || this._value === undefined
        }
        map(fn) {
            return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
        }
    }
    
    • Either函子
    • 用于处理异常
    • 类似于if...else
    • Left函子 + Right函子
    • IO函子
      • IO函子的value是一个函数,将函数作为值来处理
      • IO函子可以把不纯的动作存储到_value中,延迟执行(惰性执行)
    class IO extends Functor {
        static of(value) {
            return new IO(function() {
                return value
            })
        }
        map(fn) {
            return new IO(fp.flowRight(fn, this._value))
        }
    }
    
    • Task函子
      • 用于处理异步任务
    const { task } = require('folktale/concurrency/task')
    const { split, find } = require('lodash/fp')
    const fs = require('fs')
    
    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: e => console.log(e),
            onResolved: d => console.log(d),
        })
    
    • Pointed函子
      • 实现了of静态方法的函子
      • of静态方法避免了使用new 来创建对象
      • 更深层的含义是of方法用来把值放到上下文Context(把值放到容器中,使用map来处理)
    • Monad函子
      • 一个函子具有join和of两个方法,并遵守一定规律,就是Monad函子
      • 可以变扁的Pointed函子
  • folktale 一个标准的函数式编程库

    • 提供了compose、curry等函数
    • 提供了Task、Maybe、Either等函子
    • 用法自行百度

你可能感兴趣的:(学习笔记(一)—— 函数式编程)