一文读懂什么是函数式编程
随着react的流行,函数式编程受到越来越多的关注,加上vue3也开始运用了函数式编程,而且这也是面试官常问的问题。所以学习函数式编程是很有必要的。
那什么是函数式编程呢?它实际运用在哪些场景下呢? 相信你看完这篇文章就明白了。
全文导图:
函数式编程可以说一种编程范式,我们常听说的编程范式还有面向过程编程、面向对象编程。这三者有什么区别呢?给大家举个例子。
面向过程编程:按照步骤一步步地实现。
let num1 = 2;
let num2 = 3;
let sum = num1+num2;
console.log(sum);
面向对象编程:把现实世界的事物和事物之间的联系抽象到程序世界中的类和对象,通过封装、继承、多态来演示事物间的联系。
function createPerson (name, age) {
this.name = name;
this.age = age;
this.show = function () {
console.log(`my name is ${this.name}`)
}
}
const cPerson = new createPerson('php', 18);
cPerson.show();
函数式编程:是一种映射关系,就像我们数学中的 y=f(x) ;同样的 x ,经过 f(x) 运算得到同样的 y ,是对运算过程的抽象。它有个核心的概念——纯函数。
(一)纯函数有两个条件
a:相同的输入始终会得到相同的输出,而且没有任何可观察的副作用。
b:函数内部不会依赖和影响外部的任何变量。
对于条件a,举个例子。
var arr = [1,2,3,4,5];
// 非纯函数
arr.splice(0,3); //=> [1,2,3]
arr.splice(0,3); //=> [4,5]
arr.splice(0,3); //=> []
// 纯函数
arr.slice(0,3); //=> [1,2,3]
arr.slice(0,3); //=> [1,2,3]
arr.slice(0,3); //=> [1,2,3]
从上面的例子里看出,对于slice来说,它对于相同的输入总能返回相同的输出;而splice直接在原数组上作出改变,产生了可观察到的副作用,即改变了数组。
对于条件b:函数内部不会依赖和影响外部的任何变量。
// 非纯函数
let mini = 18;
function checkAge1(age) {
return age >= mini
}
console.log(checkAge1(20))
// 纯函数
function checkAge2(age) {
let mini = 18;
return age >= mini
}
console.log(checkAge2(20))
在 checkAge1 函数中依赖全局变量mini,一旦mini被改变,输出结果就会改变。而 checkAge2 中,无论任何时候,传递相同的参数 age ,结果都不会被影响。
(二)纯函数代表——Lodash
安装
npm i lodash
引入
const _=require('lodash');
const array=['jack','php','gb','luck'];
console.log(_.first(array));//jack
console.log(_.last(array));//luck
(三)纯函数优势
由于纯函数的相同输入有相同输出和不被外部环境所影响两个特点,给函数式编程带来了三大好处:可缓存、可测试、并行处理。
a:可缓存
因为纯函数对相同的输入始终有相同的结果,所以可以把纯函数的结果缓存起来。
b:可测试
纯函数让测试更方便
c:并行处理
在多线程环境下并行操作共享的内存数据很可能会出现意外情况
纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数
(一)高阶函数
所谓高阶函数就是函数作为参数或者返回值是函数的函数。例如:
函数作为参数
例如模拟js中的map函数。
const map = (array,fn)=>{
let results = [];
for(let value of array){
results.push(fn(value));
}
return results;
}
//测试
let arr = [1,2,3,4,5];
arr = map(arr,v=>v*v);
console.log(arr)//[ 1, 4, 9, 16, 25 ]
以上就是把fn函数作为参数传递进去。类似的js中的forEach、filter,every、some等都可以把函数作为参数传递进去,所以都是高阶函数。
let arr = [1,2,3,4,5];
arr.forEach(function(i)=>{
});
arr.filter(function(i)=>{
return i>4;
});
返回值是函数
function makeFn(){
let msg='hello function';
return function(){
console.log(msg)
}
}
const fn = makeFn();
fn();
(二)柯里化
函数柯里化就是当函数有多个参数,可以对这个函数进行改造,只传递部分参数。并且让这个函数返回一个新的函数,新的函数再接受剩余的参数并且返回相应的结果。例如判断一个字符串是否含有数字:
function haveMatch(reg) {
return function (str) {
return str.match(reg);
}
}
const haveNum = haveMatch(/\d+/g);
console.log(haveNum('aa22'));//['22']
在lodash中提供了一个通用的柯里化方法——curry
curry:本身是个纯函数,传进去的参数是纯函数,那返回的也是纯函数。例如以上柯里化函数经过 curry 改造。
const _ = require('lodash');
const curried = _.curry((reg,str)=>{
return str.match(reg);
});
const haveNum = curried(/\d+/g);
console.log(haveNum('aa22'));//['22']
以上如果传递 haveMatch 所需的部分参数,那么会返回一个函数,等待继续传递参数。直到传递所有参数,返回最终结果。使用 curry 的目的是把多元函数最终转化为一元函数。
(三)函数组合
如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数。就像是数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道最终结果。函数组合默认从右到左执行。
fn = compose(f1,f2,f3);
b = fn(a);
现在做一个函数组合演示,假如现在有个需求:将数组做三步处理,(1)转化为大写字母;(2)颠倒顺序排列;(3)取出第一个数组
function toUpper(array){
return array.map(item=>item.toUpperCase());
}
function reverse(array){
return array.reverse();
}
function first(array){
return array[0]
}
//函数组合演示
function compose(a,b,c){
return function(arr){
return a(b(c(arr)))
}
}
const comFn = compose(first,reverse,toUpper);
const result = comFn(['php','gb','pwb'])
console.log(result);//PWB
以上就是将每一步都封装成一个函数,然后将多个函数重新组合成一个新函数,得到最终结果。这样划分多个粒度的函数可以任意组合成我们所需要的函数。例如,将数组转化为大写,然后取出第一个数组。
const comFn = compose(first,toUpper);
const result = comFn(['php','gb','pwb']);//PHP
在lodash中提供了函数组合方法——flowRight
const _ = require('lodash');
const toUpper = item => item.toUpperCase();
const reverse = arr => arr.reverse();
const first = arr => arr[0];
const f = _.flowRight(toUpper, first, reverse);
console.log(f(['php','gb','pwb']));//PWB
最后的话:
函数式编程是一种编程思想,想要掌握一种编程思想是需要很长时间的,可能是半年,可能更长时间。
虽然在编码中不需要全部使用函数式编程的思想,但是我们可以尽量多掌握一些编程思想,这样在实际工作中就可以从多方面比较,找出相对更高效的编码方式。
以上完,希望对你有所帮助。
往期精选:
END