如何能通过增加代码的健壮来提高程序的可靠性是每一个高级前端要面对的问题,函数式编程就是一个必备的利器。
函数式编程的概念
函数式编程是一种编程范式,是一种构建计算机程序结构和元素的风格,它把计算看作是对数学函数的评估,避免了状态的变化和数据的可变。
与之相对应的就是“命令是编程”
例如:给数组每项取平方操作,命令式编程:详细的命令机器去完成我们想要的结果;看例子:
let a = [1,2,3,4,5];
for(let i=0;i<a.length;i++){
a[i] = a[i]*a[i];
}
console.log(a);//[1, 4, 9, 16, 25]
上面这段代码就是经典的命令式编程,它除了完成a的每项平方值外不能重用,离开了上下文环境它几乎没有任何作用。
如果我们将上面的代码进行函数封装:
let getSquare = (a)=>{
let result = [];
for(let i =0;i<a.length;i++){
result.push(a[i]*a[i]);
}
return result;
}
getSquare([2,3,4,5])
(4) [4, 9, 16, 25]
虽然我们将求数组的平方方法进行了封装,可以满足其他地方使用,但是如果此时需求更改为求每项平方数值再加1,那么这个方法就有废了,要不就需要修改方法体的代码,显然不够灵活,依然是命令式编程。
如果应用函数式编程去处理上面问题:
对问题进行函数式分析,有一个数组,我们需要对数组的每一项值进行计算,然后输出新的数组。
let a = [1,2,3,4,5];
let hadle = (a,fn)=>{
let result = [];
for(let i =0;i<a.length;i++){
result.push(fn(a[i]));
}
return result;
}
let calculate = (v)=>{
return v*v+1;
}
let value = hadle(a,calculate);
console.log(value)//[2, 5, 10, 17, 26]
可以看到这样处理,当需求再次变化为求每项平方值减1时,我们只需要修改calculate方法即可其余不变,增强了代码的稳定性。
函数式编程主张
将复杂的问题用多个函数分解为多个简单的函数,然后再组合调用,这样做的好处就是保留不变函数,通过尽量少的可变函数来增加程序稳定性。
因为我们要对复杂问题进行分解,分解的标准是什么:
纯函数
判断一个函数是否是纯函数:
- 如果给定相同的参数,则返回相同的结果(也称为确定性),仅仅依赖于输入的参数。
- 它不会引起任何副作用。
看例子:商品打折计算
let discount = 0.8;
let calculatePrice = (price,num)=>{
price*num*discount;
}
let price = calculatePrice(20,2)
显然上面calculatePrice方法不是纯函数,因为它除了依赖商品单价price和个数num外还依赖了打折率discount,如果作为全局变量的discount变化了0.9,即使相同的price和num输出的结果是不同的。我们需要再次更改将discount变为参数
let discount = 0.8;
let calculatePrice = (price,num, discount)=>{
price*num*discount;
}
let price = calculatePrice(20,2,discount)
这样calculatePrice方法就是纯函数,除了三个参数的依赖在没有其他依赖,与此同时没有对源程序造成任何副作用。
我们看一个产生副作用的例子:
let discount = 0.8;
let numObj = {
numArry:[1,2,3]
}
let calculatePrice = (price,numObj, discount)=>{
let num = 0;
for(let i=0;i<numObj.numArry.length;i++){
num+=numObj.numArry[i];
}
numObj.numArry = [];
return price*num*discount;
}
let price = calculatePrice(20,numObj,discount)
console.log(price)
console.log(numObj)
结果:
上面的代码虽然做到了除参数不依赖其他,但是执行方法后我们更改了外部变量numObj的值(引用传值) 即对外部产生了副作用。
也不能称为严格意义上的纯函数。
再例如js数组的方法 :
不会改变原来数组的纯函数有:
concat()—连接两个或更多的数组,并返回结果。
every()—检测数组元素的每个元素是否都符合条件。
some()—检测数组元素中是否有元素符合指定条件。
filter()—检测数组元素,并返回符合条件所有元素的数组。
indexOf()—搜索数组中的元素,并返回它所在的位置。
join()—把数组的所有元素放入一个字符串。
toString()—把数组转换为字符串,并返回结果。
lastIndexOf()—返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。
map()—通过指定函数处理数组的每个元素,并返回处理后的数组。
slice()—选取数组的的一部分,并返回一个新数组。
valueOf()—返回数组对象的原始值。
-----------分割线-------------------
会改变原来数组的有:
pop()—删除数组的最后一个元素并返回删除的元素。
push()—向数组的末尾添加一个或更多元素,并返回新的长度。
shift()—删除并返回数组的第一个元素。
unshift()—向数组的开头添加一个或更多元素,并返回新的长度。
reverse()—反转数组的元素顺序。
sort()—对数组的元素进行排序。
splice()—用于插入、删除或替换数组的元素。
可变性与不可变性
可变性指的是一个变量创建之后可以被任意修改,
不可变性指的是变量被创建后不能再被修改,是函数式编程追求的核心目标。
例如要解决引用数据类型不可变性的问题,我们就引申出对象的深拷贝,后面的学习中会对深拷贝问题展开详细说明。
总结
函数式编程是一种思想,完全做到纯函数式的编程是几乎不可能的,但作为高级开发人员必须要时刻提醒自己用函数式去解决问题,这是成长的必然之路,vue react框架组建思想都是基于函数式编程的,理解他对于阅读源码有很大帮助。