无所事事的样子开始了摸鱼的一天
函数式编程
- 面向对象的编程思维方式
通过抽象成类、对象,通过封装、继承和多态来实现
- 函数式编程的思维方式
根据输入通过某种运算,获得相应的输出
函数式编程的函数不是指程序中的函数(方法),而是数学中的函数(映射关系)
相同的输入始终要获得相同的输出(纯函数)
函数式编程用来描述数据(函数)之间的映射
万能的函数
js中函数是一个普通的对象,可以存储到变量/数组中,它还可以通过作为参数和返回值
// 函数作为参数
function forEach(array,fn){
for(let i = 0;i<array.length;i++){
fn(array[i]);
}
}
let arr=[1,9,8,6,1,4,3,4];
forEach(arr,function(item){
console.log(item);
})
// for循环提前写好,调用的时候代码简单,便于阅读
function filter(array,fn){
let results = [];
for(let i = 0;i<array.length;i++){
if(fn(array[i])){
result.push(array[i]);
}
}
}
let result = filter(arr,function(item){
return item >= 5;
});
console.log(result);
// 封装好的函数可以让代码更好的复用,也可以写的更少、更快
// 函数作为返回值
function makeFn(){
let msg = 'Hello';
return function(){
console.log(msg);
}
}
makeFn()();
// 可以开始套娃操作了
// 有木有发现,其实这是个闭包
// 来来来,实现个once函数,让函数只执行一次
function once(fn){
let done = false;
return function(){
if(!done){
done = true;
return fn.apply(this,arguments);
}
}
}
let pay = once(function(money){
console.log(`支付:${money}元`);
});
pay(5);
pay(5);
pay(5);
// 这只会执行一次
// 高阶函数是用抽象来帮我们屏蔽细节,只需要关注目标即可
// 类似于市面的UI组件库的感觉,你用就一个标签你不需要管我里面的实现有多烂
闭包(让你自闭的包子)
可以在另一个作用域调用一个函数的内部的函数并访问到该函数的作用域中的成员 本质是,函数执行时会放到一个执行栈上,函数执行完,会从执行栈上移除,但是堆上的作用域成员被外部引用不能释放,因此内部函数依然可以访问外部函数中的成员
// 封装一手Math.pow();
function makePower(power){
return function(number){
return Math.pow(number,power);
}
}
let power2 = makePower(2); // 平方
let power3 = makePower(3); // 立方
// 这个平方的2,立方的3都会在这边被引用,不会被释放
纯函数(ps:就是纯爱的)
相同的输入永远会得到相同的输出,没有任何可观察的副租用。
纯函数是用来描述输入和输出之间的关系的(类似数学的函数)
// slice 返回数组指定的部分,不会改变原数组,输入相同输出就相同,所以上纯函数
// splice 对数组操作后返回该数组,改变了原数组,相同输入调用几次,这个数组就裂开了,并没有得到相同的输出,所以它一点也不纯(不是纯爱的那就上ntr了)
- 可缓存
函数相同的输入始终有相同的输出,对计算的结果可以进行缓存
function memoize(fn){
let cache = {};
return function(){
let key= JSON.stringify(arguments);
cache[key] = cache[key] || fn.apply(fn,arguments);
return cache[key];
}
}
function getArea(r){
console.log(r); // 缓存了结果就只会打印一次
return Math.PI * r * r;
}
getAreaMemoize = memoize(getArea);
console.log(getAreaMemoize(5));
console.log(getAreaMemoize(5));
console.log(getAreaMemoize(5));
- 可测试
纯函数的测试更方便,因为相同输入就有相同输出,很好测试。
- 并行处理
多线程环境下内存可以出现意外情况,纯函数不需要访问共享的内存数据,并行环境下可以运行任意运行纯函数。
函数的副作用
副作用会让函数变的不纯,如果函数依赖外部的状态就无法保证相同的输出,就会带来副作用。
副作用可能会给程序带来安全隐患,带来不确定性,但是这玩意还不是能完全禁止的,只能控制在可控范围发生。(ps:你要控几住你自己)
let mini = 18;
function checkAge(age){
return age>=mini; // 这就依赖于外部了
}
// 如果声明在函数内,就会产生硬编码,不过可以用柯里化(ps:柯基化,这么叫好像也能知道是啥的样子)
柯里化(ps:柯基化)
// 将mini作为参数解决硬编码的情况
function checkAge(mini,age){
return age>=mini;
}
checkAge(18,20);
checkAge(18,22);
checkAge(18,25);
// 这18怎么总重复呢,不美丽了呢
// 改造一手
function chechAge(min){
return function(age){
return age >= min;
}
}
let chechAge18=chechAge(18);
当函数有多个参数的时候,进行对函数的改造,调用一个函数,只传递部分参数,让它返回一个新的函数,新的函数接受剩下的参数,并返回相应的结果(ps:套娃走一波就上柯里化了)
// ES6 简写就很简单了的亚子
let chechAge = min => (age => age >= min);
模拟一手lodash 里的 curry
function curry(func){
return function curriedFn(...args){
if(args.length < func.length){
return function(){
return curriedFn(...args.concat(Array.from(arguments)))
}
}
return func(...args);
}
}
function getSum(a,b,c){
return a+b+c;
}
const curried = curry(getSum);
console.log(curried(1)(2)(3));
console.log(curried(1,2)(3));
console.log(curried(1)(2,3));
总结一手:
- 柯里化可以给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数
- 对函数参数的缓存
- 让函数变的更灵活,粒度更小
- 将多远函数转换成一元函数,可以组合使用函数产生强大的功能(ps:拼图啊这玩意,懂了懂了)
函数组合
柯里化的代码很容易出现套娃,所以要弄成函数组合的方式,不然套娃的世界可太难了
- 函数就像是一条数据的管道,函数组合就是把这些管道连接起来,让数据穿过多个管道。(国家电网??)
- 默认是从右到左执行的
function compose(f,g){
return function(value){
return f(g(value));
}
}
function reverse(array){
return array.reverse();
}
function firse(array){
return array[0];
}
const last = compose(firse,reverse);
console.log(last([4,5,6])); // 6
让我们组合一个不限制长度的compose 函数组合
// reduce 对数组每个元素执行一个执行一个由我们提供的函数,并将其汇总为一个单个函数
function compose (...args){
return function(value){
return args.reverse().reduce(function(acc,fn){
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(['ads','qwe','mdzz']));
公司这破网,呸,今天到此结束,各班按顺序带回 —————————————————————————————————————————————————————— 我这废柴