函数式编程是一种编程范式,它不是JavaScript中特有的,只能说JavaScript遵循函数式编程范式
本文主要讲解函数式编程中:纯函数、函数柯里化以及组合函数的概念和用法
文章目录
💊 纯函数 (无副作用)
1 纯函数定义
- 确定的输入参数,一定会产生确定的输出(输出不依赖外部变量,因为外部变量可能会改变)
- 不产生副作用:修改外部变量,修改传入参数,修改外部存储、触发事件
2 纯函数的特点
不依赖,不打扰
/* 判断以下函数是否为纯函数*/
let age = 18;
function bar(){
return age;
}
// bar不是纯函数,输出依赖外部变量
const obj = {name: 'xs', age: 8};
let n = 10;
function foo(obj) {
obj.age = 18;
n = 15;
}
// foo不是纯函数,修改了参数,修改了外部变量
function demo(key, str) {
localStorage.setTitem(key, str);
}
// demo不是纯函数,修改了外部存储
// 数组方法slice是纯函数,而splice不是纯函数,会修改原数组
let nums = [11, 22, 433, 21];
let newNums2 = nums.splice(2);
console.log(newNums2); // [ 433, 21 ]
console.log(nums); // [ 11, 22 ]
应用
react中规定组件必须是个纯函数,不可以修改传入的参数props的大小,因为组件会被大量复用,若一处产生了副作用,势必会为其他组件的使用带来隐患。
注意
在编程中尽可能
写纯函数,不是绝对的,比如set函数就是为了改变一些值
⏳ 函数柯里化 (一步一步传参)
柯里化currying也是函数式编程中的概念,不仅仅属于JS
1 柯里化定义
- 柯里化是一个过程:现有一个函数,它需要接收多个参数,通过柯里化,将其变成另一个函数,它只接收一部分参数,返回一个函数去处理剩余参数
- 多个参数拆分成多次函数调用的过程就是柯里化
2 柯里化举例
// 现有一个函数add,它需要接收多个参数
function add(x, y, z) {
return x + y + z;
}
// 将add柯里化为newAdd
// newAdd只接收一部分参数,返回一个函数去处理剩余参数
function newAdd(x) {
return function(y) {
return function(z) {
return x + y + z;
}
}
}
// 简写形式如下
const newAdd = x => y => z => x + y + z;
// 正常函数调用 VS 柯里化后的函数调用
add(1, 2, 3);
newAdd(1)(2)(3);
// 多个参数拆分成多次函数调用的过程就是柯里化
3 柯里化作用
单一职责原则
每一层函数只处理一个传入的参数
function add(x, y, z) {
x = x + 2;
y = y * 2;
z = z * z;
return x + y + z;
}
function newAdd(x) {
// 第一层函数只处理x
x = x + 2;
return function(y) {
// 第一层函数只处理y
y = y * 2;
return function(z) {
// 第一层函数只处理z
z = z * z;
return x + y + z;
}
}
}
逻辑复用原则
(制造工具函数)
案例1:制造加法工具函数
const add = (x, y) => x + y;
const makeAdder = x => y => x + y; // add函数柯里化
// 假设在程序中经常需要将5与其他数字相加
const add5 = makeAdder(5);
add5(10); //15
案例2:打印日志(时间,类型,信息)函数被多次调用,部分参数多次重复
const log = (date, type, message) => {
console.log(`[${date.getHours()}:${date.getMinutes()}][${type}]:[${message}]`)
}
// 如果前两个参数都一样,每次都重写好麻烦,能不能复用一下
log(new Date(), 'DEBUG', '查找轮播图bug')
log(new Date(), 'DEBUG', '查找数据bug')
log(new Date(), 'DEBUG', '查找菜单bug')
// 将log函数柯里化
const makeLog = date => type => message => {
console.log(`[${date.getHours()}:${date.getMinutes()}][${type}]:[${message}]`)
}
// 制造函数
const debugLog = makeLog(new Date() , 'DEBUG');
debugLog('查找轮播图bug')
debugLog('查找数据bug')
debugLog('查找菜单bug')
4 柯里化加工函数
构造一个myCurrying函数,输入为一个函数,输出柯里化后的函数
function myCurrying(fn) {
function curriedFn(...args1) {
if (args1.length >= fn.length){// 传入参数足够
return fn.call(this, ...args1);
} else{ // 传入参数不够,需要再返回个函数接收其他参数
return function(...args2){
return curriedFn.call(this, ...args1, ...args2);
}
}
}
return curriedFn;
}
const curriedFn = myCurrying(fn);
fn(...args);
curriedFn(...args1)(...args2)(...rest);
curriedFn.call('abc', ...args1)(...args2)(...rest); // 由于函数调用时可能绑定this,所以在函数内部也要用call方法来传递this
Tips
- fn.length可以取到函数fn参数的数目
🧅 组合函数 (像洋葱层层相套)
组合函数是个应用的小技巧,将一个函数的结果作为另一个函数的入参,像洋葱一样调用
1 组合函数举例
// 组合函数(洋葱
const g = n => n * 2;
const f = n => n ** 2;
function composeFn(f, g) {
return function(count) {
return g(f(count))
}
}
const fg = composeFn(f, g);
fg(3) //36
2 组合函数加工器的实现
function composeFn(...fns) {
// 若传入参数不是函数类型,报错
fns.forEach(fn => {
if(typeof fn !== 'function)
throw new TypeError('参数必须都为函数');
})
// 返回组合函数
return function(...args){
// JS中函数的入参可以有多个,但是输出只有一个
let ans = fns.length ? fns[0].call(this, ...args) : args;
for (let i = 1; i < fns.length; i++) {
ans = fns[i](ans);
}
return ans;
}
}