函数式编程第一弹
Posted 前端精英
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数式编程第一弹相关的知识,希望对你有一定的参考价值。
什么是函数式编程?
面向对象的编程思维方式:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和多态来演示事物事件的联系。
函数式编程的思维方式:把现实世界的事物和事物之间的联系
抽象到程序世界
函数式编程是范畴论
,范畴论是数学分支的一门很复杂的数学,彼此之间存在关系概念。箭头表示范畴成员之间的关系,名字被成为“态射”,通过“态射”,一个成员能够变形成另一个成员。所有的成员是一个集合,变形关系就是函数。y=f(x)
函数式编程是20世纪三十年代引入的一套用于研究函数定义、函数应用和递归的形式系统,函数式编程也并非就是用函数来编程,主旨是将复杂的函数复合成简单的函数。函数式编程的真正在前端圈中火热的原因 可能是来源于react中的高阶函数。
函数是一等公民
-
函数是一等公民指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他函数,可以作为参数,可以作为其他函数的返回值。 -
不可改变变量,在函数式编程中变量仅仅代表某一个表达式,变量是不能被修改的,所有的变量只能被赋一次初始值。 -
map和reduce就是最常用的函数式编程
函数式编程的特点
-
函数是一等公民 -
只用表达式,不用语句 -
没有副作用 -
引用透明(函数运行只靠参数)
声明式代码和命令式代码
命令式代码就是编写一条一条的指令去解决我们的需求
// 命令式
var fruitsName = [];
for (i = 0; i < fruits.length; i++) {
fruitsName.push(fruits[i].name);
}
// 声明式
var fruits = cars.map(fruit=>fruit.name);
函数式编程就是使用这种声明式代码
纯函数
什么是纯函数呢?对于相同的输入,得到的一定是相同的输出,而且没有任何可以观察的副作用。
举个例子:
var a = 5
function Sum(b){
return a + b
}
Sum(5)
如上图列子,它是一个纯函数吗?显然不是的。变量a是可以被修改的。我们稍作修改
const a = 5
function Sum(b){
return a + b
}
Sum(5)
这是纯函数,确定的输入,确定的输出。但不是最佳的函数,原因是它依赖了外部的环境。怎么样解决这个问题,我们看下面的这个例子。
function A(a){
return function (b){
return a + b
}
}
var B = A(5)
B(5)
首先来看函数A ,每次输入一个参数,得到的函数都是固定的,所以A函数是纯函数,再来看函数B,乍一看,好像是依赖了外部变量a,但是这个a每次都是在A函数调用之后产生的,并且调用之后是无法改变的。这样看来B函数也是一个纯函数。或许有的人会想,那我在调用一次A函数,传一个6进去,那不就改变了吗?首先你在掉一次A函数,产生的函数就不是B函数了,或许是C,或许是D。反正不是之前的那个B函数了。
高阶函数
-
函数当参数,把传入的函数做一个封装,然后返回这个封装 函数,达到更高程度的抽象。
function forEach (array,fn){
for(let i = 0 ; i < array.length ; i++){
fn(array[i])
}
}
forEach(arr,function(item){
console.log(item)
})
-
函数作为返回值
function once(fn){
let done = false
return function(){
if(!done){
done = true
return fn.apply(this,arguments)
}
}
}
let fn = once(function(){
consnole.log('我只会执行一次!!')
})
fn()
fn()
fn()
如上的例子可以很好的看出高阶函数的应用场景。还有react中的高阶组件,本质上也是高阶函数的一种扩展。
使用高阶函数的意义
-
抽象可以帮我们屏蔽细节,只需要关注与我们的目标 -
高阶函数是用来抽象通用的问题
常用的高阶函数
-
forEach -
map -
filter -
every -
some -
find/findIndex -
reduce -
sort ...
函数的柯里化
当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后不变)
然后返回一个新的函数去接收处理剩余的参数,返回结果
如上AB函数就是柯里化实现的一个例子。在接收了一个参数a,返回一个函数去处理剩下的参数b。假如有一个正则匹配的函数数。你可能会这样写。
function checkByRegExp(regExp,str){
return regExp.test(str)
}
checkByRegExp(/^1\d{10}$/, '13333333333'); // 校验手机
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '12@qq.com'); // 校验邮箱
但是如果校验的手机次数多了之后,会发现正则需要写好多次。
checkByRegExp(/^1\d{10}$/, '13333333333');
checkByRegExp(/^1\d{10}$/, '12222222222');
显然,程序员骨子里的优雅是不允许我这么做的,此处就可以用函数柯里化来优化它。
function checkByRegExp(regExp){
return function(str){
return regExp.test(str)
}
}
const checkPhone = checkByRegExp(/^1\d{10}$/)
checkPhone('13333333333')
checkPhone('12222222222')
某种意义上来讲,柯里化是一种对参数的“缓存”,是一种非常高效的编写函数的方法。
lodash中的柯里化方法
-
_curry(func)
功能:创建一个函数,该函数接收一个或多个func的参数,如果func所需要的参数都被提供则执行func并返回执行的结果。否则返回该函数并等待接收剩余的参数
参数:需要柯里化的函数
返回值:柯里化后的函数
const _ = require('lodash')
function getSum(a,b,c){
return a + b + c
}
let curried = _.curry(getSum)
curried(1)(2)(3)
curried(1,2,3)
curried(1,2)(3)
总结
-
柯里化可以让我们给一个函数 传递较少的参数得到一个已经记住了某些固定参数的新函数 -
这是一种对参数的‘缓存’ -
让函数变的更灵活,让函数的粒度更小 -
可以把多元函数转换成一元函数,可以组合使用函数产生强大的功能
注:多元函数指的就是多个参数的函数
函数组合
为了解决函数嵌套的问题,避免写出洋葱式代码
// 翻转数组
const reverseArr = x => x.reverse()
// 取第一个元素
const first = x => x[0]
//那我们取出第最后一个元素的代码将会是这个样子的
first(reverseArr([1,2,3,4,5]))
假如我们有个函数,取出一个数组最后一个元素。首先这个函数有两个步骤,1.翻转数组,2.拿出数组第一个元素。如上代码在调用之后变成洋葱式代码 h(g(f(x)))
,为了解决这个问题,把代码拉平。我们就需要用到函数组合。
const compose = (f,g)=>(x=>f(g(x)))
const getLastValue = compose(first,reverseArr)
getLastValue([1,2,3,4,5])
我们虽然写了很多代码来实现取出数组最后一个元素的功能,但是大家别忘记了我们写的这些辅助函数可以任意的组合。函数式编程可以最大程度上使得函数被重用
lodash中的组合方法
-
lodash中的组合函数flow()或者flowRight(),他们都可以组合多个函数 -
flow()是从左到右运行 -
flowRight()是从右到左运行,使用的更多一些
const _ = require('lodash')
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()
const fn = _.flowRight(toUpper,first,reverse)
console.log(fn(['one','two','three']))
函数组合结合律
函数组合需要满足结合律
我们既可以把g和h组合,还可以把f和g组合,结果都是一样的
//结合律
let f = compose (f,g,h)
let associative = compose(compose(f,g),h) === compose(f,compose(g,h))
// true
PointFree风格
是函数式编程的一种风格,可以把数据处理的过程定义成与数据无关的合成运算。不需要用到代表数据的那个参数,只要把简单的运算步骤合成一起,然后通过组合函数进行组合。
-
不需要指明处理的数据 -
只需要合成运算过程 -
需要定义一些辅助的基本运算函数
const fn = str => str.toUpperCase().split(' ')
fn('pointfree')
在这个函数中,我们使用了str这个变量,毫无意义。那么如何用pointFree风格去改变它呢。
const split = x => x.split(' ')
const toUpperCase = x => x.toUpperCase()
const fn = compose(split,toUpperCase)
fn('pointfree')
首先将两个原生的方法改造成纯函数,然后就可以将两个方法组合在一起。可以让这两个方法复用性更强。
总结
本章内容带大家基本了解了一下函数式编程的概念,当然这只是开胃菜
以上是关于函数式编程第一弹的主要内容,如果未能解决你的问题,请参考以下文章