简单提一下,一等公民都具有如下特性:javascript
写过JS的人确定都知道函数是能知足上述的特性。前端
函数式编程是一种编程范式,其中函数定义的是表达式树,每一个表达式都返回一个值,而不是改变程序状态的命令语句。由于函数是 JavaScript 的一等公民,因此能够吧函数做为其余函数的参数或者返回值,这样就能够将其中小功能以模块化的方式组合在一块儿。java
能够经过禁止更改外部状态和数据来定义纯函数,纯函数是只依赖实际参数,无论任何全局或者局部的状态。既输入相同的参数,输出的内容永远都是同样的。git
const sum = (x, y) => x + y;
sum(1, 2) // 3
复制代码
原生例子编程
let arr = [1, 2, 3];
arr.slice(0,1); // [1]
arr.slice(0,1); // [1]
arr // [1, 2, 3]
arr.splice(0,1); // [1]
arr.splice(0,1); // [2]
arr // [3]
复制代码
由上可见数组的 slice
方法是纯函数,不会改变原对象;splice
改变了原对象,致使每次操做自身都发生变化,因此不是纯函数。小程序
使用纯函数时咱们发现程序出现了与预期不符的状况,也就是输出的值不是想要得值,那么只用检查输入的参数就OK。数组
上面提到纯函数,输入值不变,那么输出值也会不变,这里就能够对比输入的参数是否发生变化,来决定是否要从新渲染来实现一个缓存优化。缓存
const memoize = f => { // 缓存函数
let cache = {};
return a => { // 这里为了方便 就只接收一个参数了
const prevValue = cache[a];
if (prevValue) {
console.log(`cache ${prevValue}`);
return prevValue;
}
return cache[a] = f(a); // 缓存纯函数的值
}
}
const double = x => x * 2; // 运算函数(纯函数)
const f = memoize(double);
f(2); // 4
f(2); // cache 4
f(8); // 16
f(8); // cache 16
复制代码
柯里化是吧接收多个参数的函数变成接收单一参数的函数,剩下的参数再经过返回的函数来进行接收。 简单理解就是把函数拆的更细,返回的函数依赖第一个参数进行计算,能够缩小适用范围,建立一个针对性更强的函数。微信
const sum = x => y => x + y;
sum(1)(2); // 3
复制代码
将 sum
函数转为只接收一个参数,并返回一个函数,再次调用将获得结果。 或许有人以为这样写纯属蛋疼,那么就举一个业务中会用到的一个例子。咱们须要一个能够将金额转为千分位的函数,可是金额的单位不定,有多是分,有多是角等。网络
const formatMoney = (money, step) => {
let str = (money / step).toFixed(2); // 两位小数
const index = str.indexOf('.');
if (index > 3) {
const start = str.substring(0, index).replace(/\B(?=(?:\d{3})+$)/g, ','); // 增长千分位符号
return start + str.substring(index);
};
return str;
}
formatMoney(1000000, 100); // 分 10,000.00
formatMoney(123456, 100); // 分 1,234.56
formatMoney(123456, 10); // 角 12,345.60
复制代码
传入两个参数 (money, step)
金额和单位,在函数中依据单位来实时金额。可是每次都要传单位,形成重复的代码。
咱们能够将上述方法转为柯里化函数:
const formatMoney = step => money => {
let str = (money / step).toFixed(2); // 两位小数
const index = str.indexOf('.');
if (index > 3) {
const start = str.substring(0, index).replace(/\B(?=(?:\d{3})+$)/g, ','); // 增长千分位符号
return start + str.substring(index);
};
return str;
}
const pennyMoney = formatMoney(100); // 单位是分
pennyMoney(1000000); // 10,000.00
pennyMoney(123456); // 1,234.56
const dimeMoney = formatMoney(10); // 单位是角
dimeMoney(1000000); // 100,000.00
dimeMoney(123456); // 12,345.60
formatMoney(1)(1000000); // 1,000,000; // 元
复制代码
咱们经过柯里化的方式预先传入单位,返回一个针对该单位的格式化方法,这样咱们就能在不一样的状况下加以复用,其中 pennyMoney
与 dimeMoney
也能加以复用,不用在每次都传入要经过什么单位来格式化。
高阶函数是一个函数,他能够把其余函数做为参数输入或者做为其返回值输出。
原生的方法有不少都是高阶函数,例如 Array.prototype.map
方法,他接收一回调函数,从回调函数中获取返回值,再使用这些值建立一个新的数组并返回。
上述咱们经过柯里化的方式优化了格式金额的方法,能够看到柯里化的方式也会返回一个函数,那么咱们能够认为,他也是高阶函数。下面咱们将再自定义一些高阶函数,让咱们更加理解高阶函数的应用。
我有一个方法会根据窗口大小进行动态加载对应的组件,既然须要动态加载组件那么相对于网络、内存等消耗是要大一些的,那么咱们就能够进行一个防抖操做,让窗口大小发生变化时不必要那么实时变化。
const debounce = (func, wait) => {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func(...args);
}, wait)
}
}
window.addEventListener('resize', debounce(() => {
if (window.matchMedia('(min-width:768px)')) {
// loader component
}
console.log('resize');
}, 500))
复制代码
上述代码实现了一个 debounce
函数,将接收一个函数和一个等待时间做为参数,返回一个新的函数,函数执行时会作一个延时防抖的操做。这样在窗口频繁的 resize
时短期内也不会屡次触发加载组件的方法。固然该防抖函数还能应用于其余不少场景。
const compose = (...funcs) => arg => {
return funcs.reduce((val, f) => f(val), arg);
}
复制代码
这是一个很经典的高阶函数,他接收多个函数做为参数,返回一个函数,返回的这个函数呢会接收一个参数。那他的做用是什么呢?能够执行如下代码看看。
const compose = (...funcs) => arg => {
return funcs.reduce((val, f) =>{
console.log(val);
return f(val);
}, arg);
}
const pennyTransform = money => money / 100;
const fixedMoney = money => (+money).toFixed(2);
const thousandthMoney = str => {
const index = str.indexOf('.');
if (index > 3) {
const start = str.substring(0, index).replace(/\B(?=(?:\d{3})+$)/g, ',');
return start + str.substring(index);
};
return str;
};
const formatMoney = compose(pennyTransform, fixedMoney, thousandthMoney);
formatMoney(1000000); // 10,000.00
// log =>
// 1000000
// 10000
// 10000.00
复制代码
上述代码依旧是格式化金额,只不过换了种写法。咱们能够从输出的 log 中看出,咱们的方法 compose
是吧全部的方法进行一个组合,依次调用,将上一个函数的返回值传入给下一个参数(第一个参数为调用时传的参数)。这样咱们就能够将函数功能拆分的很细,一个只作一件事,每一个都是纯函数,每个很小的功能就是一个粒子,咱们能够随意将其组合拆分,使其应用场景更普遍。
不少同窗或许搞不懂 compose
这个函数具体的逻辑,接下来就仔细讲一讲。能够在代码中看到最重要的 reduce
这个函数,这是数组原生的一个方法,能够先看看MDN上对这个方法的描述 Array.prototype.reduce:方法会对数组中的每个元素执行传入的函数,再将其汇总结果返回。
接收两个参数:
callback
执行数组中每一个值,此函数会接收四个参数,这里咱们只看用到的前两个参数:
accumulator
累计器累计回调的返回值,简单理解就是上一个调用回调函数的返回值。若是是第一个次调用那么就是下面的 initialValue
或者 undefined
。
currentValue
数组中当前正在处理的元素。咱们数组内的元素都是方法,也就是这里将是当前须要执行的方法。
initialValue
第一次调用回调函数传的值。
const compose = (...funcs) => arg => {
return funcs.reduce((val, f) =>{
// 第一次执行 val 为传入的 arg
return f(val); // 将 val 传入 currentValue(当前须要执行的方法),这里也会将函数调用的结果进行当即返回,返回的值将会在执行到下一个函数时当 val 使用。
}, arg);
}
复制代码
大体讲了讲函数的执行流程,方便理解,有问题欢迎评论区留言,有什么逻辑BUG、字打错了什么的也欢迎纠正。
参考
本来打算顺带把高阶组件写一写,想想仍是放到下一个文章里面吧,打算写一写 React的高阶组件与自定义Hook。有兴趣的朋友能够点点关注,说不定我啥时候就更新了,如今这篇文章四月份就开始写了,断断续续一直写到了七月份,也忙、也懒。
最后再打个广告,有上海公司要招前端么,3年工做经验,主要使用React,Vue、小程序什么的也都用过、服务端渲染也搞过。要求:不要是外派、不要99六、不要频繁加班,最好闲一些,工资少点也是阔以滴,这样就有时间写写文章了。个人微信号 jaceyi
。