什么是函数式编程?
在学习函数式编程之前, 我们先来了解一些与之相关的概念, 以便于我们更好的理解函数式编程思想:
命令式编程
首先我们要明确的是什么是命令式编程, 这是与函数式编程相对应的一种编程方式, 在日常开发中, 我们大多数使用命令式编程. 命令式编程就是详细的命令机器怎么做来达到我们想要的结果.
举个例子:
有一个需求, 将数组中的每一项元素乘以 2, 我们很容易写出以下代码(代码示例 1), 这就是典型的命令式编程:
- let multip2=(arr)=>{
- let newArr=[];
- for(let i=0; i<arr.length;i++)
- {
- newArr.push(newArr[i] * 2);
- }
- return newArr;
- }
- console.log(multip2([1,2,3,4])); // [2,4,6,8]
这段代码看起来没什么问题, 但是其实是没法重用的, 如果我们还需要对数组进行其他操作, 我们仍需再次遍历数组, 然后实现逻辑
纯函数
如果函数的调用参数相同, 则永远返回相同的结果; 它不依赖于程序执行期间函数外部任何状态或数据的变化, 必须只依赖于其传入参数. 即相同的输入, 永远会得到相同的输出.
同样来看一段代码示例, 帮助我们理解纯函数和非纯函数:
- // 非纯函数
- const reg=/^(\+|-)?\d+(\.\d+)?$/ig;
- const isNumber = function (value) {
- const v = value.toString();
- return reg.test(v);
- };
- console.log(isNumber(3.22)); // 相同的输入, 输出可能会出现不同的结果, 依赖于外部变量 reg 是否发生改变
- // 纯函数
- const regValidate = function (value, reg) {
- const v = value.toString();
- return reg.test(v);
- };
- console.log(regValidate(3.22, /^(\+|-)?\d+(\.\d+)?$/ig)); // 相同的输入, 结果相同, 同时这个函数可以被复用, 不仅仅用来验证数字
- // 甚至可以进一步抽象出两个纯函数, 以使后期调用更加简便
- const isNumber=(value)=>{
- return regValidate(value, /^(\+|-)?\d+(\.\d+)?$/ig);
- }
正确的使用纯函数可以保证更加高质量的代码, 也是一种更加干净的编码方式
函数副作用
当调用函数时, 除了返回函数值以外, 还对主调用函数产生了附加的影响, 比如更改了全局变量 (函数外部的变量) 或者修改了参数.
还是通过代码来说明:
- // 以下代码中, test 函数里改变了外部变量 a 的值, 调用 test 之后, 再次使用 a 变量就具有不确定性了
- // 这就是给程序带来了副作用
- let a=10;
- let test=()=> a=a+1;
- test();
- console.log(a); //11
函数副作用会给程序设计带来不必要的麻烦, 给程序带来难以查找的错误, 并且降低程序的可读性
纯函数是没有副作用的.
可变性和不可变性
可变性是指一个变量创建以后可以任意修改; 不可变性是指一个变量一旦创建, 就永远不会发生改变, 不可变性是函数式编程的核心思想.
仍然是结合代码来解释:
- // 可变性, 调用 test 方法后, data 的属性值发生改变
- let data={count:1};
- let test=(data)=>{
- data.count=2;
- }
- console.log(data.count);// 1
- test(data);
- console.log(data.count);// 2
- // 我们可以通过改造 test 函数, 使得数据不发生改变
- let test=(data)=>{
- // 这里也可以通过扩展属性来深拷贝{...data}, 但是扩展属性只能深拷贝数组或对象的第一层
- let newData=JSON.parse(JSON.stringify(data));
- newData.count=2;
- }
- console.log(data.count);// 1
- test(data);
- console.log(data.count);// 1
函数式编程
了解了前面的这些概念, 我们再来看看函数式编程是怎么回事以及如何实现的吧.
"函数式编程是一种编程范式. 它把计算当成是数学函数的求值, 从而避免改变状态和使用可变数据. 它是一种声明式的编程范式, 通过表达式和声明而不是语句来编程." 这是维基百科上对函数式编程的定义.
简单来说, 就是将复杂的函数逻辑抽象出简单函数, 将运算过程尽量写成一系列嵌套函数调用; 将一个任务拆分成一系列最小颗粒的函数, 通过组合的方式来完成任务, 跟组件化的思想很类似.
与命令式相对, 函数式编程本质上是一种声明式编程, 我们在编码时只需要完成所需要功能的代码, 无需关心其内部实现, 唯一需要考虑的是在这些内部实现中是否会产生副作用.
函数式编程必须满足两个要求:
纯函数
数据不可变
我们用函数式编程思想, 来将第一段代码 (代码示例 1) 改造一下:
- let arr=[1,2,3,4];
- let arrMap=(arr, fn)=>{
- let newArr=[];
- for(let i=0; i<arr.length;i++)
- {
- newArr.push(fn(arr[i]));
- }
- return newArr;
- }
- let multip2= (item)=>item * 2;
- let product=arrMap(arr,multip2);
- // 如果有新需求, 将数组中的元素变为字符串格式
- // 我们只需要添加一个 tostring 的函数就可满足
- let tostring= item=>item.toString();
- const strArr=arrMap(arr, tostring);
总结
诚然, 函数式编程思想远不止如此, 仍然需要更加的深入探究和大量的实践. 然而即使如上述粗浅的学习心得, 我们也不难发现函数式编程可以带来以下好处:
代码简洁, 通过使用大量函数, 重用, 减少了重复的代码, 提高编码效率
提高代码可靠性, 得益于纯函数, 不依赖, 也不会改变外界的状态
易于测试维护, 每一个函数都可以被看做独立单元, 很有利于进行单元测试和调试, 以及模块化组合
可读性强, 声明式编程方式更接近自然语言
虽然清楚了函数副作用的弊端, 但在开发中我们也无法完全消除函数副作用, 只能尽可能的减少函数副作用:
函数入口使用参数运算, 而不去修改参数
函数内不去修改函数外部的变量
运算结果通过函数返回给外部
通过 "依赖注入" 的方式让函数变 "纯", 将依赖的变量提取出来, 通过参数传给函数
来源: http://www.jianshu.com/p/8d4683681e10