注意:
// 非函数式
let num1 = 2
let num2 = 3
let sum = num1 + num2
console.log(sum)
这段代码是非函数式的,我们是通过步骤一步一步实现的,所以它是面向过程的编程方式。
如果使用函数式的思想实现这个功能:首先我们要对运算过程进行抽象,我们要计算两个数的和,我们会抽象一个add的函数,且相同的输入要有相同的输出。
// 函数式
function add (n1, n2) {
return n1 + n2
}
let sum = add(2, 3)
console.log(sum)
函数式编程的好处是我们后续可以无数次地重用,而且在函数式编程过程中,我们抽象出来的函数都是细粒度的函数,我们将来可以把这些函数组合成功能更强大的函数。
在 JavaScript 中函数就是一个普通的对象 ,我们可以把函数存储到变量/数组中,它还可以作为另一个函数的参数和返回值,甚至我们可以在程序运行的时候通过 new Function(‘alert(1)’) 来构造一个新的函数。
// 把函数赋值给变量
let fn = function () {
console.log('Hello First-class Function')
}
fn()
//一个实例
const BlogController = {
index(posts) {
return Views.index(posts)}
}
上面的案例中我们可以看出:index方法和内部调用的方法有相同的形式(它的参数和返回值是一样的),我们就可以把Views.index赋值给index。
注意:我们是要把这个函数的方法赋值给另外一个方法,不是把函数的调用赋值给另一个方法,所以要把方法后的调用去掉。
//可以优化为
const BlogController = {
index : Views.index
}
高阶函数 (Higher-order function)
函数作为参数
//通过仿写forEach和filter来理解 函数作为参数
function forEach (array, fn) {
for (let i = 0; i < array.length; i++) {
fn(array[i])
}
}
// 测试
let arr = [1, 3, 4, 7, 8]
// forEach(arr, function (item) {
// console.log(item)
// })
function filter (array, fn) {
let results = []
for (let i = 0; i < array.length; i++) {
if (fn(array[i])) {
results.push(array[i])
}
}
return results
}
// 测试
let arr = [1, 3, 4, 7, 8]
let r = filter(arr, function (item) {
return item % 2 === 0
})
console.log(r)
我们上面写了两个函数,如果函数作为参数,它可以让我们变得更灵活,而且我们在调用的时候不用考虑内部是怎么实现的,这个函数把内部实现的细节帮我们屏蔽了,而且我们函数的名字是有实际意义的,比如forEach,通过名字就知道它是要去遍历;而filter,通过它的名字就知道是要去过滤数据。
函数作为返回值我们会在下节闭包中详细讲解。
使用高阶函数的意义:
//不需要考虑函数内部实现的细节
let array = [1, 2, 3, 4]
// 面向过程的方式
for (let i = 0; i < array.length; i++) {
console.log(array[i])
}
// 高阶函数
forEach(array, item => {
console.log(item)
})
前一小节中我们通过仿写forEach和filter来简单理解了一下函数作为参数下来我们通过几个函数作为返回值的例子来理解下闭包:
下面的例子:如果我们在一个函数里面又返回了一个函数,并且在我们访问的这个函数的内部又访问了外部函数中的成员,其实这就是闭包。
// 函数作为返回值
function makeFn () {
let msg = 'Hello function'
return function () {
//匿名函数
console.log(msg)
}
}
// const fn = makeFn()
// fn()
makeFn()()
根据上面的描述,其实闭包的核心作用就是把makeFn的作用范围延长了,正常情况下,makeFn执行完成之后,msg会被释放掉,但是如果makeFn中返回了一个成员,并且外部对这个成员有引用,那此时,makeFn内部再执行完成过后就不会被释放,因为外部对内部有引用。
接下来我们来写一个once函数,once我们在jQuery中可能见过,Jquery中的once函数是给一个DOM元素去注册事件,这个事件只会执行一次。在lodash其实也有一个once函数,lodash中的once是对一个函数只执行一次,我们来模拟一下once函数:
//我们通过闭包来实现一个只能执行一次的函数
function once (fn) {
// 假设这是一个支付的函数
let done = false
return function () {
// 在这里我们不确定传参个数
if (!done) {
done = true // 已经被执行
//arguments是具有数组某些特性的'类数组'(伪数组)
//每个函数都有一个Arguments对象实例arguments
//它引用着函数的实参,可以用数组下标的方式'[]'引用arguments的元素
return fn.apply(this, arguments)//通过arguments来取实参
}
}
}
let pay = once(function (money) {
console.log(`支付:${
money} RMB`)
})
// 只会支付一次
pay(5)
pay(5)
支付:5 RMB
以上是我们once函数的实现,通过once函数返回了一个函数。
接下来通过案例来体会闭包。假设在一个项目中,经常会求数字的幂,我们可以通过Math.pow去实现,但假设我们经常去求一个数字的二次方、三次方,我们就经常要去重复写,我们可以简化下:
// 生成计算数字的多少次幂的函数
function makePower (power) {
//power:幂
return function (x) {
return Math.pow(x, power)
}
}
// 求平方
let power2 = makePower(2)
// 求三次方
let power3 = makePower(3)
let array = [1, 2, 3, 4, 5]
// 纯函数
console.log(array.slice(0, 3))
console.log(array)
console.log(array.slice(0, 3))
// 不纯的函数
console.log(array.splice(0, 3))
console.log(array)
console.log(array.splice(0, 3))
console.log(array)
[ 1, 2, 3 ]
[ 1, 2, 3, 4, 5 ]
[ 1, 2, 3 ]
[ 1, 2, 3 ]
[ 4, 5 ]
[ 4, 5 ]
[]
// 自己写一个求和的纯函数
function getSum (n1, n2) {
return n1 + n2
}
console.log(getSum(1, 2)) // 3
console.log(getSum(1, 2)) // 3
3
3
函数式编程不会保留计算中间的结果,比如上面的getSum函数:我们在调用getSum这个函数的时候,会给它传递一个参数,并且调用这个函数获取到它执行的结果,而函数内部的变量我们的结果是无法去获取到的 ,所以我们可以认为在函数式编程中,它的变量是不可变的,也就是无状态的。
另外,我们在基于函数式编程的过程中,我们会经常需要一些细粒度的纯函数,我们要自己去写这些细粒度的纯函数的时候要写非常非常多,就不太方便,但我们不用担心这些细粒度怎么来,因为有很多函数式编程的库,比如说lodash。
当我们有了这些细粒度的函数之后,后面讲到的函数组合中,我们可以把这些细粒度的函数组合成更能更强大的函数,当我们在调用这些函数的过程中,我们就可以把一个函数的执行结果交给另一个函数继续去处理。
首先初始化package.json
npm init -y
npm i lodash
当lodash安装好以后定义一个常量把lodash引用进来
const _ = require('lodash')
引用完lodash以后,我们来演示几个数组的方法,所以先来定义一个数组
const array =