函数式编程笔记
前言#
面向对象的编程思维方式:把现实中的事务抽象成程序世界中的类和对象,通过封装、集成和多态来演示事物间的联系
函数式编程的思维方式:把显示世界的事物和事物之间的联系抽象到程序世界(对运算过程进行抽象)
函数式编程的特点#
在JS中函数就是一个普通的对象,我们可以把函数存储到变量/数组中,它还可以作为另一个函数的参数和返回值,甚至我们可以在程序运行的时候通过new Function('alert(1)')来构造一个新的函数
函数是一等公民
函数可以存储在变量中
函数作为参数
函数作为返回值
高阶函数#
高阶函数:1. 可以把函数作为参数传递给另一个函数;2. 可以把函数作为另一个函数的返回结果。
使用高阶函数的意义: 1. 抽象可以帮我们屏蔽细节,只需要关注我们的目标;2. 高阶函数是用来抽象通用的问题
// 高阶函数-函数作为参数-更灵活
function filter(array, fn) {
let results = []
for(let i = 0;i < array.length; i++) {
if(fn(array[i])) {
results.push(array[i])
}
}
return results
}
// 高阶函数-函数作为返回值 闭包
function makeFn () {
let msg = 'hello'
return function() {
console.log(msg)
}
}
const fn = makeFn()
fn()
makeFn()()
常用高阶函数: forEach filter map every some
// map
const map = (arr, fn)=>{
let results = []
for(let i of arr) {
reuslts.push(fn(i))
}
return results
}
// every
const every = (arr, fn)=>{
let result = true
for(let i of arr) {
result = fn(i)
if(!result) {
break
}
}
return result
}
// some
const some = (arr, fn)=>{
let result = false
for(let i of arr) {
result = fn(i)
if(result) {
break
}
}
return result
}
闭包#
函数和其周围的装的引用捆绑在一起形成闭包
可以再另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员
闭包的本质:函数在执行的时候会放到一个执行栈执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员
// lodash中的once函数 -只执行一次 闭包
function once(fn) {
let done = false
return function () {
if(!done) {
done = true
return fn.apply(this, arguments)
}
}
}
闭包案例
function makePower(power) {
return function (number) {
return Math.pow(power,number)
}
}
let power2 = makePower(2)
let power3 = makePower(3)
console.log(power2(4))
console.log(power2(5))
console.log(power3(4))
纯函数#
相同的输入永远会得到相同的输出,而且没有任何可观察的副作用
纯函数的好处
可缓存
// 记忆函数
function getArea (r) {
console.log(r)
return Math.PI * r * r
}
let getAreaWithMemory = _.memoize(getArea)
// 模拟 memoize 实现
function memoize(f) {
let cache = {}
return function () {
let key = JSON.stringify(arguments)
cache[key] = cache[key] || f.apply(f, arguments)
return cache[key]
}
}
可测试
纯函数让测试更方便
并行处理
在多线程环境下并行操作共享的内存数据可能会出现以外情况
纯函数不需要访问共享的内存数据,多以在并行环境下可以任意运行纯函数(web worker)
副作用
副作用让一个函数变的不纯
副作用来源: 配置文件 / 数据库 / 获取用户的输入
所有外部交互都有可能带来副作用,副作用也使得方法通用性下降不适合扩展和可重用性,同时副作用会给程序带来安全隐患,但是副作用不能拿完全禁止,尽可能控制他们在可控范围内发生
柯理化#
// 柯理化
function checkAge(min, age) {
return age >= min
}
function checkAge (min) {
return function (age) {
return age >= min
}
}
let checkAge = min=>(age=> age> = min)
let checkAge18 = checkAge(18)
let checkAge19 = checkAge(19)
console.log(checkAge18(20))
console.log(checkAge19(24))
当一个函数有多个参数的时候先传递一部分参数调用它
然后返回一个新的函数接收剩余的参数,返回结果
lodash中的柯理化函数
_.curry(func)
function getSun(a,b,c) {
return a+b+c
}
const curried = _.curry(getSum)
console.log(curried(1,2,3))
console.log(curried(1)(2,3))
console.log(curried(1,2)(3))
const macth = _.curry((reg, str)=> {
return str.match(reg)
})
// curry 实现
function curry(func) {
return function curriedFn(...args) {
// 判断实参和形参的个数
if(args.length<function func.length) {
return function() {
return curriedFn(...args.contact(Array.from(arguments)))
}
}
return func(...args)
}
}
总结
柯理化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数
这是一种对函数的”缓存“
让函数更灵活,让函数的粒度更小
可以把多元函数换成一元函数,可以组合使用函数产生强大的功能
函数组合#
纯函数和柯理化容易写出洋葱代码
从右到左执行
function compose(f,g){
return function (value) {
return f(g(value))
}
}
// lodash 中的组合函数
_.flowRight()
function compose(...args) {
return function (value) {
return args.reverse().reduce(function(acc, fn){
return fn(acc)
}, value)
}
}
const compose = (...args)=>value=>args.reverse().reduce((acc,fn)=>fn(acc), value)
lodash-fp模块
const f = fp.flowRight(fp.join('-'), fp.map(fp.toLower), fp.split(' '))
console.log(f('NEVER SAY DIE'))
// point free
const f = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower)
Functor 函子#
什么是函子
容器:包含值和值的变形关系(这个变形关系就是函数)
函子:是一个特殊的容器,通过一个普通对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理
class Container{
constructor(value) {
this._value = value
}
map(fn) {
return new Container(fn(this._value))
}
}
let r = new Container(5).map(x=>x+1).map(x=>x*x)
class Container{
statuc of (value) {
return new Container(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return new Container(fn(this._value))
}
}
let r = Container.of(5).map(x=>x+1).map(x=>x*x)
MayBe 函子
class MayBe {
statuc 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 === undefind
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误