什么是函数式编程
函数式编程是编程范式之一,我们常听说的编程范式还有面向过程编程,面向对象编程。
面向对象编程的思维方式:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物事件的联系
-
函数式编程的思维方式:把现实世界的事物和事物之间的联系抽象到程序世界(对运算过程进行抽象)
- 程序的本质:根据输入通过某种运算获得相应的输出,程序开发过程中会涉及很多有输入和输出的函数
- 函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数即映射关系,例如:y = sin(x),x和y的关系
- 相同的输入始终要得到相同的输出(纯函数)
- 函数式编程用来描述数据(函数)之间的映射
// 非函数式 let num1 = 2 let num2 = 3 let sum = num1 + num2 console.log(sum) // 函数式 function add (n1, n2) { return n1 + n2 } let sum = add(2, 3) console.log(sum)
高阶函数
- 高阶函数
- 可以把函数作为参数传递给另一个函数
- 可以把函数作为另一个函数的返回结果
- 函数作为参数
// filter
function filter (array, fn) {
let results = []
for (let i = 0; i < array.length; i++) {
if (fn(array[i])) {
results.push(array[i])
}
}
return results
}
- 函数作为返回值
// once
function once (fn) {
let done = false
return function () {
if (!done) {
done = true
return fn.apply(this, arguments)
}
}
}
let pay = once(function (money) {
console.log(`支付:${money} RMB`)
})
// 只会支付一次 pay(5)
pay(5)
pay(5)
pay(5)
使用高阶函数的意义
- 抽象可以帮我们屏蔽细节,只需要关注与我们的目标
- 高阶函数是用来抽象通用的问题
闭包
-
闭包(Closure):函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。
-
可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员
// 函数作为返回值 function makeFn(){ let msg = 'hello function' return function() { console.log(msg) } } const fn = makeFn() fn()
-
-
闭包的本质:函数在执行的时候会放到一个执行栈上当函数执行完毕之后会从执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员
function makePower (power){ return function (x){ return Math.pow(x, power) } } let power2 = makePower(2) let power3 = makePower(3) console.log(power2(4)) console.log(power3(4))
纯函数
纯函数概念
-
纯函数:相同的输入永远会得到相同的输出,而且没有任何可观察的副作用
- 纯函数就类似数学中的函数(用来描述输入和输出之间的关系),y = f(x)
-
数组的
slice
和splice
分别是:纯函数和不纯的函数-
slice
返回数组中的指定部分,不会改变原数组 -
splice
对数组进行操作返回该数组,会改变原数组
let numbers = [1, 2, 3, 4, 5] // 纯函数 numbers.slice(0, 3) // => [1, 2, 3] numbers.slice(0, 3) // => [1, 2, 3] numbers.slice(0, 3) // => [1, 2, 3] // 不纯的函数 numbers.splice(0, 3) // => [1, 2, 3] numbers.splice(0, 3) // => [4, 5] numbers.splice(0, 3) // => []
-
- 函数式编程不会保留计算中间的结果,所以变量是不可变的(无状态的)
- 我们可以把一个函数的执行结果交给另一个函数去处理
纯函数的好处
-
可缓存
- 因为纯函数对相同的输入始终有相同的结果,所以可以把纯函数的结果缓存起来
const _ = require('lodash') function getArea (r) { return Math.PI * r * r } let getAreaWithMemory = _.memoize(getArea) console.log(getAreaWithMemory(4))
- 自己模拟一个memoize函数
function memoize (f) {
let cache = {}
return function () {
let arg_str = JSON.stringify(arguments)
cache[arg_str] = cache[arg_str] || f.apply(f, arguments) return cache[arg_str]
}
}
- 可测试
- 纯函数让测试更方便
- 并行处理
- 在多线程环境下并行操作共享的内存数据很可能出现意外情况
- 纯函数不需要访问共享的内存数据,所以在并行环境下可以任意允许纯函数
副作用
- 纯函数:对于相同的输入永远会得到相同的输出,而且没有任何可观察的副作用
// 不纯的
let mini = 18
function checkAge (age) {
return age >= mini
}
// 纯的(有硬编码,后续可以通过柯里化解决) function checkAge (age) {
let mini = 18
return age >= mini
}
副作用让一个函数变的不纯,纯函数的根据相同的输入返回相同的输出,如果函数依赖于外部的状态就无法保证输出相同,就会带来副作用。
副作用来源
- 配置文件
- 数据库
- 获取用户的输入
- ….
所有的外部交互都有可能带来副作用,副作用也使得方法通用性下降不适合扩展和可重用性,同时副作用会给程序中带来安全隐患给程序带来不确定性,但是副作用不可能完全禁止,尽可能控制它们在可控范围内发生。