记录一笔 个人理解的函数式编程

对于函数式编程个人一直不是很了解,于是抽空查看相关资料整理笔记下

1、概念

在编程思想上讲,目前有三类:命令式(面向过程)面向对象函数式,简单介绍下这三种

命令式编程 ———— 求和

let a = 10
let b = 20
let sum = a + b

可以看到是多个语句或者指令等让计算机执行一些动作

面向对象编程 ———— 把现实中的事物抽象成程序中的对象,通过封装继承多态来演示事物之间的联系

函数式编程 ———— 求和

 function add(x,y) {
    
    
     return x + y
  }
add(a,b)

通过函数数据进行了转换,类似于数学中的函数 y = sin(x),强调的是输入输出的一种映射关系;同时还可以不断的串联多个函数对数处理(xxx.split(’ ‘) .map(v => v.slice(0, 1).toUpperCase() + v.slice(1)).join(’ '))

2、函数式编程的部分特性

列举一些个人感觉不是很好理解的

无副作用

// 有副作用
let a = 10 
function getSum(b) {
    
    
	a++ // 修改了外部变量,类似的还有数组的 splice 会修改原数组
	return a + b
}

// 没有副作用
function getSum(b) {
    
    
	// 没有修改原数据
	return a + b
}

透明引用

函数只会用到传递的参数或者内部固定的参数

// 根据id获取列表信息
function getList(id) {
    
    
	// 只用参数id
	axios(id).then(res => {
    
    
		...
	})
}

// 不透明的
let id = 10
function getList() {
    
    
	// 用了外部的变量
	axios(id).then(res => {
    
    
		...
	})
}

关于透明引用补充一些:在使用vue时,可能有些获取数据的函数是如下写法

methods: {
    
    
    // 获取xxx列表
    getList() {
    
    
    	// 函数依赖 this.id 这个变量,而不是参数/内部固定参数
    	axios.getList({
    
     id: this.id }).then(res => {
    
    
    		...
    	})
    },
    // 透明引用
    getList(id ) {
    
    
    	// 函数依赖传递的参数
    	axios.getList({
    
     id }).then(res => {
    
    
    		...
    	})
    }
  },

上述透明引用,避免了无用变量的创建,id 完全可以通过传递的方式,而非在data中再定义一个,同时别人也无需进入getList函数,就可以知道该函数根据 id 获取列表

3、函数式编程的几种模式

高阶函数闭包纯函数柯里化函数组合,还有函子等概念(这个目前笔者还未曾遇到过,所以暂时跳过)

4、高阶函数

概念
1、可以把函数作为参数传递给另外一个函数
2、可把函数作为另外一个函数的返回值

使用高阶函数,我们可以把一些处理编写成高阶函数,具体想要实现什么,由自己传入的函数决定

比如数组的 filter,要想过滤只需要按需求传入过滤的函数即可,相反,不使用这个高阶函数,是不是还是得for循环去每次判断? 当然这里不是想表达 filter 这些优于 for ,只是想表明高阶函数意义,不需要关注具体实现细节,只需要传入自定义条件,即可实现结果

比如数组的 map、filter、forEach等
我们不需要关心它内部如何实现
比如:想要过滤一些数据,就传递自己编写的过滤条件调用即可

函数作为返回值

// 编写一个只执行一次的函数,貌似也有闭包那味儿
function once(fn) {
    
    
    let done = false
    return function () {
    
    
        if(!done) {
    
    
            done = true
            return fn.call(this,arguments)
        }
    }
}
let getNum = once(function() {
    
    
     console.log('看我打印几次')
 })
 getNum() //看我打印几次 
 getNum() //不会执行
 getNum()

高阶函数其实大家在实际项目中用到的也比较多,只是没发觉而已

5、闭包

概念
简单来说,闭包的格式就是函数嵌套,内层函数对外层函数作用域仍有引用,导致外部函数变量不会被销毁。其实你经常在写闭包,只是没发现

// 闭包好处之一 作缓存
function cheackAge(base) {
    
    
    return function (age) {
    
    
        对 base 一直有引用
        return age > base
    }
}
//比如这种情况,需要对年龄是否大于 18 进行判断,我们可以写成闭包形式,
//首次调用时,会将基准值缓存起来
//再返回一个函数,且返回的这个函数无法对 基准 进行修改,最大程度保证了纯函数的特性
let is18 = cheackAge(18)
console.log(is18(20))

闭包这个话题,相信不用聊太多,毕竟各类面经谈的不少,所以这里不着重讲

6、纯函数

概念
相同的输入,只会得到相同的输出,好比数学公式,S = π * r²,只要r一样,面积始终不会变

对于上例,完全可以写成一个普通比较函数 age > base,如果是这样 base 势必会从外界传入,既然是外界传入,就少不了被修改的风险,就不能百分之百保证函数是纯的

相反,我们通过闭包方式,第一此就将比较基准缓存下来,后续比较使用返回的新函数是不可能修改到比较的基准值(18),因此是纯的

//对于数组
slice --- 纯的(不会改变原数组,所以每次slice(0,3)得到结果相同)
splice -- 不纯(要改变原数组,所以每次调splice(0,3)结果都不一样)

//纯的 只要a b 相同,结果总是相同的
function getSum (a,b) {
    
    
    return a + b
}
// ps 纯函数不会保留计算中间值,计算中也没有什么变量,不会影响结果,没有副作用

副作用补充:
我们说纯函数的作用就是,相同的输入只会得到相同的输出,如果无法保证这点,则这个函数不纯,也就是有副作用

副作用 — > 让函数变的不纯,因为结果会到(外部,或者内部)受影响
所以,当函数的状态 依赖外部作用无法保证相同输出时,就会带来副作用

比如: 配置文件用户输入、vue 的watchcomputed(都依赖外部变量)

纯函数,由于有相同的输入只会得到相同的输出特性,我们可以利用其作缓存(因为相同的输入,总是得到相同的输出,所以没必要再次计算)

// 利用纯函数 编写一个记忆函数
function memory(fn) {
    
    
    const map = new Map()
    return function (...arg) {
    
    
        console.log(map)
        let k = JSON.stringify(arg)
        let value = map.get(k) || fn(...arg)
        map.set(k, value)
        return value
    }
}
let memoredFoo = memory(foo)
console.log(memoredFoo(1,2,3))

7、柯里化

概念
当一个函数有多个参数,可以先传递一部分参数调用它(这部分固定参数不变),然后返回一个已经记住某些固定参数的新函数,继续接收参数,
返回的函数会记住之前固定的参数,同时也是函数参数的一种缓存

多元函数转换为一元函数,可使用函数组合 组合成更强大的函数

//多元函数化解为一元函数
function foo(a, b, c) {
    
    
    console.log('在计算了')
    return a + b + c
}

function currie(fn) {
    
    
    return function bar(...arg1) {
    
    
        // 参数个数相等 返回值
        if (fn.length == arg1.length) {
    
    
            return fn.apply(this, arg1)
        }
        // 继续返回一个函数 接收参数
        return function (...arg2) {
    
    
            return bar.apply(this, arg1.concat(arg2))
        }
    }
}
let test = currie(foo)
// 将之前的多元函数 foo(1,2,3) 转换为了 test(1)(2)(3) 一元函数格式
test(1)(2)(3)

有了柯里化,可以顺利将多元化解为一元,化解为一元后,再通过函数的组合(好比文章开头的例子:xxx.split(’ ‘) .map(v => v.slice(0, 1).toUpperCase() + v.slice(1)).join(’ '))后,完成复杂功能

在Lodash 库中,也有编写好的curry函数,同时还有很多其他优秀的工具函数。对于函数式编程这一思想,只有实际中慢慢体会,多思考别人的写法。

猜你喜欢

转载自blog.csdn.net/qq_45219069/article/details/124366547