写在前言
最近学了不少东西,让我耳目一新就是这个函数式编程,虽然以前我都是用面向对象编程方式,这次一接触函数式编程就感觉思路开拓不少,很多人都说函数式是未来的方向,其实我也只是赞同一部分,一个是的确写法比较优雅,解耦,但是呢用了不少闭包,可能存在性能问题,但是总而言之,还是值得一学,毕竟像react,readux就用了大量这种编程风格写法
函数式编程好处
- 函数式编程抛弃 this
- 更加方便 tree shaking
让开发者更加专注于业务代码的开发
类型
- 函数作为返回值
- 函数作为参数
//比如
arr.reduce((item)=>fn)
函数作为返回值,也就是闭包
const onece = (fn) => {
const times = false;
return () => {
if (!times) {
times = true;
return fn.apply(this, arguments);
}
};
};
如果比如有一个需求,要计算不同级别的员工的工资,只需要把这种工资类型赋值给一个函数,然后调用这个函数就可以进行其他的计算
function makeSalary(baseSalary) {
return (money) => {
return baseSalary + money;
};
}
let level1 = makeSalary(12000)
let level2 = makeSalary(15000)
console.log(level1(3000));
console.log(level2(5000))
纯函数
另外纯函数,对相同的输入始终有相同的输出,而且没有任何可观察的副作用 比如slice,但是像splice就是不纯的函数,就是说对原数据进行修改就不属于纯函数 相对于函数式编程而言,必须是纯函数
纯函数的好处:
- 可缓存
- 可测试,为了单元测试使用
缓存
这里有一个计算圆面积的方法,我们通过一个cache变量就可以把圆面积计算保存下来,用key值作为唯一标识符,下次才有相同参数进来则使用缓存里面的东西,大大的提高性能
这种使用场景让我想起了斐波那契数列计算优化,也是通过类似的手法进行优化,通过一个cache变量存储每次递归的值
function getArea(r) {
console.log(Math.PI);
return Math.PI * r * r;
}
function memery(fn) {
let cache = {};
return function () {
let key = JSON.stringify(arguments);
return (cache[key] = cache[key] || fn.apply(fn, arguments));
};
}
let fn = memery(getArea);
console.log(fn(4)); // 会发现只打印一次PI
console.log(fn(4));
console.log(fn(4));
console.log(fn(4));
console.log(fn(4));
console.log(fn(4));
柯里化
柯里化:当一个函数有多个参数的时候,我们可以先传递一部分参数返回一个新函数,然后在用新函数去接收剩余的参数
function checkAge(min){
return function(age){
return age>=min
}
}
let checkAge18 = checkAge(18)
console.log(checkAge18(29));
//通过es6来改造
const checkAge=(min)=>(age)=>age>=min
比如有个需求是过滤字符串中有没有数字或者空格
//面向过程写法
str.match(/\s+/g)
str.match(/\d+/g)
//函数式编程
//这里得使用loadash
const match = curry((reg,str)=>{
return str.match(reg)
})
const hasSpace = match(/\s+/g)
const filter = curry((func,arr)=>{
return arr.filter(func)
})
let findSpace = filter(hasSpace)
console.log(findSpace(['dsaf asfd', '测试有空 格', '测试没空格']));
实现神奇的curry函数
如果大家有印象,应该记得18年还是17年,阿里有一道面试题就是要实现这个curry函数,手写这个函数,实现add(1)(2,3); a(1,2,3);a(1)(2)(3)
这种计算方式
其实也很简单,就是通过比较形参和实参数量,如果实参小于形参数量,说明还是没有完全传入全部参数,否则立即进行计算,执行函数
function curry(fn) {
return function curriedFn(...args) {
if(args.length<fn.length){
return function(){
// 这里比较关键,参数必须按照顺序来
return curriedFn.apply(curriedFn, [...args, ...arguments]);
}
}
return fn.apply(fn, args);
};
}
函数组合
但是纯函数和柯里化容易写出洋葱圈代码,比如a(b(c()))
这时候就要用函数组合来解决这个问题
这时候要涉及到一个概念,管道,每一个函数都是一个管道节点,
函数组合默认是从右到左执行的
function compose(...args) {
return function (value) {
// 从后往前,然后使用reduce
return args.reverse().reduce((acc, fn) => {
// 把value 传进来,然后第一次acc就是value的值,下次acc就是fn(value)的值
return fn(acc);
},value);
};
}
const reverse = (arr) => arr.reverse();
const first = (arr) => arr[0];
const toUpper = (s) => s.toUpperCase();
const f = compose(toUpper, first, reverse);
console.log(f(['one', 'tow']));
//es6简写
const compose =(...args)=>(value)=>args.reverse().reduce((acc,fn)=>fn(acc),value)
结合律:其实就是数学中的结合律,先结合某个括号里的内容
const f = compose(toUpper, compose(first, reverse));
另外,组合函数里面的函数只能接受一个参数,因此如果有多个参数必须得使用curry对函数进行柯里化改造 调试:通过log函数打印出每一次的结果
const _ = require('lodash')
// 进行柯里化是为了先穿一部分参数,另外一部分参数等待管道进来
const split = _.curry((sep, str) => _.split(str, sep));
const join = _.curry((sep,arr)=>_.join(arr,sep))
const map = _.curry((fn,arr)=>_.map(arr,fn))
const log = _.curry((tag,msg)=>{console.log(tag,msg);return msg})
// lodash的_.map模块是数据优先,如果用fp.map则是方法优先,就不用通过curry包装
const f = _.flowRight(join('-'), log, map(_.toLower), log('split 之后'), split(' '));
console.log(f('NEW YORK CITY'))
point free模式
point free模式,不需要指明处理的数据,只需合成运算过程,需要定义一些辅助的基本运算函数, 其实就是函数的组合
const fp = require('lodash/fp')
const firstLetterToUpper = fp.flowRight(fp.join('. '),fp.map(fp.flowRight(fp.first,fp.toUpper)),fp.split(' '))
console.log(firstLetterToUpper('word wild web '));
函数式编程应用
比如给页面上一个id名为test的div添加文字,然后我们把这个方法封装起来,方便以后其他div进行类似操作,比如添加一个dom结构什么的。
我们这里使用函数式编程只关注于对这些操作进行合并即可,具体业务逻辑不用关注,大大解耦
const curry = (fn) => {
return function handleCurry(...args) {
if (args.length < fn.length) {
// 实参小于形参
return function () {
return handleCurry.apply(handleCurry, [...args, ...arguments]);
};
}
return fn.apply(fn, args);
};
};
const compose = (...args) => {
return function (value) {
return args.reverse().reduce((acc, fn) => {
return fn(acc);
}, value);
};
};
const getDom = (className) => {
return document.querySelector(className);
};
const setColor = curry((color, dom) => {
dom.style.color = color;
});
const setContent = curry((text, dom) => {
dom.innerHTML = text;
return dom;
});
const log = (msg) => {
return msg;
};
let f = compose(setColor('blue'), setContent('ddd'), log, getDom);
f('#test');
``
本文使用 mdnice 排版