文章目录
前言
随着对前端开发学习的深入,发现函数式编程相较于面向过程、面向对象的编程规范,更为贴合JavaScript的开发标准。
一、什么是函数式编程?
首先,函数式编程和面向过程、面向对象编程一样,是一种编程规范;
其次,它可以把功能分解为一系列的独立的函数,通过函数间的互相调用来完成功能。
二、为什么使用函数式编程?
1. 函数式编程能提高复用性和可拓展性
每一个函数就是一块积木,我们可以随时拼入新的积木,随时能够取出积木去复用。
//假设我们现在需要 将一个数字乘以2,再减去1。
//面向对象(ES6中有了类,设置了相关的关键字,这里还是采用传统的组合继承的方式编写。)
function CalcNumber(number) {
this.number = number;
}
CalcNumber.prototype.calc = function(){
return this.number * 2 - 1;
}
//假设此时我们需要 将一个数字乘以2,再减去1,再加上2。
function CalcNumberSon(number) {
CalcNumber.call(this, number);
}
CalcNumberSon.prototype = new CalcNumber();
CalcNumberSon.prototype.calc = function(){
return this.number * 2 - 1 + 2;
}
//函数式编程
function multiplyTwo(num) {
return num * 2;
}
function minusOne(num) {
return num - 1;
}
//假设此时我们需要 将一个数字乘以2,再减去1,再加上2。
function addTwo(num) {
return num + 2;
}
var result = multiplyTwo(10);
result = minusOne(result );
result = addTwo(result );
//由以上对比我们可以看出,当需求改变时,函数式编程更加灵活,可复用性、可拓展性更强。
2. Tree-shaking(摇晃树webpack中的功能)
Tree-shaking的本质是通过文档流的引入判断是否使用某个方法(就是没有用到的部分,不进行最后的打包操作),但是面向对象的编程方案无法记录。
3. 如何写好函数式编程?
3.1. 写好函数式编程的要求
3.1.1. 保证纯函数
一个函数的返回结果只依赖于他的参数,同样的输入必定有同样的输出
// 非纯函数
var a = 10;
function add(num) {
return a + num; //这个函数的返回结果依赖于外部的a变量,当外部的a变量发生改变时,传入相同的参数,不一定会得到相同的结果。
}
add(5) //结果是15
a = 5;
add(5) //结果是10
//纯函数
var a = 10;
function add(a, num) { //在这里我们将a也作为参数传进来,这样就满足了我们纯函数的定义。
return a + num;
}
3.1.2. 减少函数副作用
函数副作用就是函数会影响外部的数据(例如:全局变量)。
// 副作用
var a = 10;
function aPlus() {
a += 1; //我们这里的操作,明显改变了外部变量a的值
return a;
}
//解决上面的副作用
var a = 10;
function aPlus(a) { //在这里我们同样也将a作为参数传进来,这样函数里面的变量就不会影响到全局的a变量了。
a += 1;
return a;
}
//当传入的参数为引用类型时(例如:对象、数组),我们可以先进行一次拷贝(例如:使用ES6的Object.create()方法、拓展运算符[...arr]),再执行操作。
4.工程化下的函数式编程
//ES6导出(mode.js)
export function fn1() {
}
export function fn2() {
}
//ES6导入
import * as all from "./mode.js"
import {fn1} from "./mode.js"
//commonjs导出
function fn1() {
}
function fn2() {
}
exports.fn1 = fn1;
exports.fn2 = fn2;
//commonjs导入
const all = require("./mode.js");
const fn1 = require("./mode.js").fn1;
5.函数式编程后,执行中的一些问题
5.1. 值的传递,写起来不方便
我们如果要连续的执行一系列函数,并且传递计算某个值,会写起来很难受。
5.2. 连续调用,写起来很麻烦
连续调用一系列函数,写起来很麻烦。
例如:
function multiplyTwo(num) {
return num * 2;
}
function minusOne(num) {
return num - 1;
}
function addTwo(num) {
return num + 2;
}
function addThree(num) {
return num + 3;
}
var result = multiplyTwo(10);
result = minusOne(result );
result = addTwo(result );
result = addThree(result );
6. Compose函数和Pipe函数
6.1. Compose函数
Compose函数可以理解为,为了方便我们连续执行方法,把自己调用传值的过程封装了起来,我们只需要给compose函数我们要执行的哪些方法,它会自动执行。
function multiplyTwo(num) {
return num * 2;
}
function minusOne(num) {
return num - 1;
}
function addTwo(num) {
return num + 2;
}
function addThree(num) {
return num + 3;
}
//var result = multiplyTwo(10);
//result = minusOne(result );
//result = addTwo(result );
//result = addThree(result );
//Compose函数
function compose() {
const args = [].slice.apply(arguments); //将参数列表转化为数组;
//slice() 方法可从已有的数组中返回选定的元素。原数组不改变返回新选定的元素组成的新数组;
//apply方法将[].slice方法的this指向了arguments。
return function (num) {
var _result = num;
//普通方式
/*for (var i = args.length - 1; i >= 0; i--) { //传入参数的执行顺序,从右往左
_result = args[i](_result);
}
return _result;*/
//采用js封装好的高阶函数reduceRight()方法(和 reduce() 功能是一样的,不同的是 reduceRight() 从数组的末尾向前执行)。
return args.reduceRight((res,cb) => cb(res),num); //第一个参数(res,cb) => cb(res),初始值num。
//res是初始值, 或者计算结束后的返回值。
//cb是当前元素(也就是传进来要执行的函数)。
}
}
compose(addThree,addTwo,minusOne,multiplyTwo)(10);
6.2. Pipe函数
Pipe函数和Compose函数功能一样,不同的是Pipe函数传入参数的执行顺序,从左往右。
function multiplyTwo(num) {
return num * 2;
}
function minusOne(num) {
return num - 1;
}
function addTwo(num) {
return num + 2;
}
function addThree(num) {
return num + 3;
}
//var result = multiplyTwo(10);
//result = minusOne(result );
//result = addTwo(result );
/result = addThree(result );
//Pipe函数
function pipe() {
const args = [].slice.apply(arguments); //将参数列表转化为数组;
//slice() 方法可从已有的数组中返回选定的元素。原数组不改变返回新选定的元素组成的新数组;
//apply方法将[].slice方法的this指向了arguments。
return function (num) {
var _result = num;
return args.reduce((res,cb) => cb(res),num);
}
}
pipe(multiplyTwo,minusOne,addTwo,addThree)(10);
7. 链式调用
我们可以用promise来组织成一个链式调用(*这里的链式调用和面向对象的链式调用有区别——面向对象链式调用是return this;)。
function multiplyTwo(num) {
return num * 2;
}
function minusOne(num) {
return num - 1;
}
function addTwo(num) {
return num + 2;
}
function addThree(num) {
return num + 3;
}
//var result = multiplyTwo(10);
//result = minusOne(result );
//result = addTwo(result );
//result = addThree(result );
//用promise组织链式调用
Promise.resolve(10).then(multiplyTwo).then(minusOne).then(addTwo).then(addThree).then((res)=>{
console.log(res);
})
总结
革命尚未成功,同志仍需努力。