为什么学习函数式编程
- 函数式编程是随着React的流行收到越来越多的关注,React中的高阶组件使用高阶函数老实现,高阶函数就是函数式编程的一个特性;再就是React中的redux也是使用了函数式编程的思想
- iOS中的自动布局框架Masonry也是使用函数式编程实现的
- 在新发布的vue3中也是用了大量了高阶函数,由此可见在流行的框架中(React、vue、Masonry)都趋向于函数式编程,也可以说你可以补血新框架,但是不能不学函数式编程
- 使用函数式编程时可以最大限度的抛弃烦人的this
- 使用函数式编程在打包过程中可以更好的利用tree shaking过滤掉无用代码
- 使用函数式编程还能方便测试、并行处理等操作
- 很多流行库能帮助我们使用函数式编程,如:lodash、underscore、ramda等
什么是函数式编程
函数式编程(Functional Programming,FP),FP是编程的一种范式,我们常见的编程范式还有面向对象、面向过程。
- 面向对象编程:就是把现实世界中的事物抽象成编程世界中的类和对象,通过封装、集成和多态来演示事物事件中间的联系
- 函数式编程:就是把现实世界中的事物和事物之间的联系抽象到程序世界(对运算过程进行抽象)
- 程序的本质:根据输入通过某种运算获得相应的输出,程序开发过程中会涉及到很多输入输出函数
- 使用数学中的映射表示:x -> f(联系/映射) -> y,即:y=f(x);再比如:y=sin(x)
- 特点:相同的输入始终得到相同的输出(即纯函数)
- 一句话描述函数式编程:用来描述数据(函数)之间的映射,是对运算过程的抽象
代码示例:计算两个数的和
// 非函数式(按步骤,面向过程)
let num1 = 1
let num2 = 2
let sum = num1 + num2
// 函数式(把过程抽象成函数)
function add(n1, n2) {
return n1 + n2
}
let sum = add(1, 2)
函数是一等公民
一等公民,MDN解释:First-class Function 在JavaScript中函数就是一个普通的对象,既然是对象,那我们就可以把函数存储到变量/数组/对象中,它还可以作为其他函数的参数和返回值使用,也可以再程序运行过程中通过 new Function('alert("xx")')来构造一个新的函数
函数可以存储在变量中
直接代码演示
// 函数赋值给变量
let fn = function() {
console.log('first-class Function')
}
fn()
// 函数存储示例
const BlogController = {
index(posts) { return Views.index(posts) },
show(posts) { return Views.show(posts) },
create(attrs) { return Db.create(attrs) },
update(posts, attrs) { return Db.update(posts, attrs) },
destory(posts) { return Db.destory(posts) }
}
// 示例优化(通过将一个函数/方法赋值给另一个变量)
const BlogController = {
index: Views.index,
show: Views.show,
create: Db.create,
update: Db.update,
destory: Db.destory
}
函数作为参数使用
高阶函数
高阶函数: Highter-order function
- 可以把函数作为参数传递给另一个函数
- 可以把函数作为另一个函数的返回结果
示例:高阶函数-函数作为参数
// 模拟forEach
function forEach(array, fn) {
if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
for (let i = 0; i < array.length; i++) {
fn(array[i])
}
}
const forEachArr = [1, 3, 6, 8, 9];
forEach(forEachArr, function(item) {
console.log(item)
})
// 模拟过滤函数filter
function filter(array, fn) {
if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
let res = [] // 存储满足条件的数据
for (let i = 0; i < array.length; i++) {
let item = array[i]
if (fn(item)) {
res.push(item)
}
}
return res
}
const filterArr = [1, 3, 6, 8 ,9]
// 过滤数组中的偶数
const evenNumbers = filter(filterArr, function(item) {
return item % 2 === 0
})
以上两个例子我们可以看出:当函数作为参数时调用会更灵活,在使用某个功能时不需要考虑其内部是如何去实现,也达到了功能封装的效果
函数作为返回值使用
函数作为返回值,即当我们去调用一个函数的时候,该函数会给我们返回一个Function类型的数据 示例:高阶函数-函数作为返回值
function makeFn() {
if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
let msg = 'Hello function'
return function() {
console.log(msg)
}
}
// 调用makeFn得到return返回的函数
const fn = makeFn()
// 执行fn
fn()
// 也可以使用连续执行
makeFn()()
// 模拟once函数
function once(fn) {
if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
let done = false // 标记当前函数是否被执行
return function() {
if (!done) {
console.log('>>', arguments)
done = true
// 返回函数执行结果
return fn.apply(this, arguments)
}
}
}
// 模拟支付场景
const pay = once(function(money) {
console.log('支付:${money}')
})
pay(10) // 执行
// 之后的都不会执行
pay(10)
pay(10)
为什么要使用高阶函数(使用高阶函数的意义)
- 高阶函数是用来抽象通用问题的
- 抽象可以帮我们屏蔽细节,只需要关注我们的目标
- 使代码更简洁
举例:比如我们现在需要去遍历一个数组,按照面向过程的方式时我们需要使用一个for循环,定义一个循环变量,判断循环条件等操作;如果此时我们使用高阶函数对遍历这个步骤进行抽象,如上边实现的forEach函数,我们此时只需要知道forEach内部帮我们实现了循环,然后传递数据给forEach。(前边的filter函数也是如此)
常用的高阶函数
- forEach
- map
- filter
- every
- some
- find/findIndex
- reduce
- sort
- ......
map实现
// 此处我们使用ES6中的箭头函数来实现
const map = (array, fn) => {
if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
let res = []
// 此处我们使用for循环的替代for of来实现
for (let value of array) {
res.push(fn(value))
}
return res
}
const mapArr = [1, 2, 3, 4]
// 此处我们来对数组中的元素做平方运算
const squareArr = map(mapArr, value => value * value)
console.log(squareArr)
some实现(检测数组中的元素是否至少有一个满足某个条件)
const some = (array, fn) => {
if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
let res = false
for (let value of array) {
res = fn(value)
if (res) {
break
}
}
return res
}
const someArr = [1, 3, 4, 5]
// 判断数组中是否有偶数
const hasEvenNumber = some(someArr, value => value % 2 == 0)
console.log(hasEvenNumber)
every实现(判断数组中的每一个元素是否都匹配某个条件)
const every = (array, fn) => {
if (Object.prototype.toString.call(array) !== '[object Array]' || typeof fn !== "function") return
let res = true // 用于记录数组中的元素是否匹配
for (let value of array) {
res = fn(value)
if (!res) break
}
return res
}
const everyArr = null
// 判断数组中每一个元素是否大于10
const moreThenTen = every(everyArr, value => value > 10)
console.log(moreThenTen)