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

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

函数一等公民 (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等函子
    • 用法自行百度
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342