函数式的概念
函数式编程通过使用函数来将值转换成抽象单元,接着用于构建软件系统。
什么是软件系统
- 展示数据 - 纸质化 -- 信息化 -- 展示数据 (统计,复杂计算,可视化图表...)
- 模块化 - CMJ, ESM,-- 管理变量。
函数式编程和传统编程思路上的区别是什么?
- 把一个数据,最终通过一些方法,一步一步变成另外一些数据。
- 函数式编程,他更关注的是,“谓词”,需要有哪些函数?
函数式编程和命令式编程的区别
- 命令式编程,命令式编程往往是建立在直线操作和检查程序状态之上,
- 函数式编程更倾向于的是,函数式编程的思路是将程序拆分并抽象成多个函数再组装回去。
函数式编程和面向对象编程的区别
面向对象强调的是,一切皆是对象,函数式强调的是,一切皆是函数。 面向对象强调的是主语,函数式强调的是谓语。
我要做一道菜
命令式 编程:
从养一头猪开始,养大、喂养、斩杀、切肉、炸、炒、上菜。
OOP 面向对象 编程;
谁,来做,pork -- chief -- dish
函数式编程:
喂养的function;斩杀的function,制作的function;-- compose -> dish.
//写一个数组的处理函数,modifyArray
//input:[1,2,3,4,5,6] ->[2,4,6,8,10,12]
第一种:
const modifyArrCmd = (arr)=>{
const result=[];
for(let i =0 ;i<arr.length;i++){
if(arr[i]!==0){result.push(arr[i] * 2)}}
return result;
}
let arr = [0,1,2,3,4,5,6];
console.log(modifyArrCmd(arr));
第二种:面向对象 编程
//思路:
//第一步:数组要经历一个筛选函数,把0筛选掉
//第二步:数组要经理一个乘法函数,每一个数据,*2;
const modifyArrFP = (arr) => arr.filter(Boolean).map(item =>item*2);
console.log(modifyArrFP(arr));
** 4_compose.js
第三种:函数式编程
//把0筛选掉
const filterBoolean = arr => arr.filter(Boolean);
const filterBigger = num => arr => arr.filter(item => item <= num);
//过滤掉大于10的
const filterBigger10 = filterBigger(10)
//科里化,把一个乘法函数,拆解了
const mutiply = num => arr => arr.map(item=> item + num );
//每一个数据,*2
const mutiplyTwo = mutiply(2);
//每一个数据,*3
const mutiplyThree = mutiply(3);
const compose=(...rest)=>startNum=>rest.reduce((total,item)=>item(total),startNum)
//等价于
const compose=(...rest)=>startNum=>rest.reduce((total,item)=>{total=item(total)},startNum)
//fn1, fn2,fn3 -> fn3(fn2(fn1(startNum)))
//把数据先 * 2 再过滤成1-10的数组
const modifyArr2 = compose(filterBoolean,mutiplyThree,filterBigger10);
const arr = [0,1,2,3,4,5,6];
console.log(modifyArr2(arr));
//对修改开放,对扩展开放。
//更想说的一点是, map/filter/reduce 其实比 forEach 更加的函数式
//React比Vue更加的函数式(FPify)
** 5_decorator.js(设计模式)
const fn =()=>{decorator.js
console.log(`this is an 接口 `);
}
const bf1 = {}=>{
console.log(`this is the before function running`);
}
//请实现一个 warppedFn 包装我的fn ,让bf1 先执行
const warppedBefore = function(fn,before){
return function(...args){
before();
returnfn.apply(this,args);
}
}
const warppedFn = warppedBefore(fn,bf1);
warpperFn()
//1.before 能不能是一个数组。
//2,怎么样执行
//3.字符串的校验
const compose=(...rest)=>startNum=>rest.reduce((total,item)=>item(total),startNum)
const warppedBefore = function(fn,...bf){
return function(args){
compose(...bf)(args);//接口请求的参数进行字符串校验
return fn.apply(this,c);
}
}
const warppedFn = warppedBefore(fn,bf1,bf2,bf3);
warpperFn([0,1,2,3,4,5]);
** 6_strategy.js(策略模式)
计算一个折扣
var strategies = {
"S":salary => salary * 4;
"A":salary => salary * 3,
"B":salary => salary * 2
}
var calculateBonus = function(level,salary){
return strategies[level](salary);
}
函数式的特点
函数是第一等公民
高阶函数
- 函数可以作为返回值
- **就给函数一些组合,缓存的能力 **。
- 函数可以作为参数;
- **就给函数提供了一个包装的能力 **。
围绕着函数,取代面向过程式的代码,往往能够有以下收益:
- 表达力更加清晰,因为“一切都是函数”,通过函数的合理命名,函数原子的拆分,我们能够一眼看出程序在做什么,以及做的过程;
- 利于复用,因为“一切都是函数“,通过函数的合理命名,函数原子的拆分,我们能够一眼看出来程序在做什么,以及做的过程;
- 利于维护,纯函数和幂等性保证同样的输入就有同样的输出,在维护或者调试代码时,能够更加专注,减少因为共享带来的潜在问题。
纯函数
一个函数如果输入参数确定,输出价格是唯一确定的,那么它就是纯函数。
- 无状态
- 无副作用 - sideEffect
//副作用很大
const minusCount = ()=>{
window.count--;
}
//没那么纯,但是,没有副作用
const minusCount =(global)=>{
global.count--;
}
//tree shaking 可以做性能优化。
methods:{
//一个接口
getDataList(){
get({query:this.queryalue}).then()res=>{this.resDate=res.data;})
}
}
const setHtml = (node.html) =>{
node.innerHtml = html;
}
//array 的 splice 方法(会修改原函数)
//array 的 slice 方法,更纯净一些。
- 无关时序
- 幂等(无论调用多少次,结果是相同的。)
多人协作的开发过程中,如何去考虑副作用?
函数的编写
- 在我们编写一个函数时,比如,一个典型的接口请求,我们怎么写?
组件的封装
在我们进行一个组件的封装时,我们一个考虑哪些问题和维度?
- 副作用
- css 隔离 。css 类重名了;
- 定位
- 纯UI的组件吗 -- 把搜索,下拉框的信息,放在外面。
- 复用性
- props 越多,复用性越强,组件越难维护。 把一个组件,拷贝过来,会又什么问题?
架构的设计
- 反过来,我们思考一下,模块化,微前端,究竟是解决什么问题?副作用究竟是什么?
熵增。
- 熵 -- 代表这混乱程度。在理解了副作用的基础上,大家可以往这个角度进行延伸思考。
我对闭包的定义
- 函数执行的上下文,不是本身的词法作用域。
函数的科里化
在计算机科学中,柯里话(currying),有译为卡瑞话或加里话,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而已返回结果的新函数的技术,这个技术由克里斯托弗.斯特雷奇以逻辑学家哈斯凯尔.加里命名。 抽象 -- 适用 -- 越多,tradeoff。 具体 -- 不适用 --越少。
add(1)(2) == 3;
add(1)(2)(3) == 6;
1.函数的柯里话。
2.当我去使用这个函数的时候,我能够转成value。
const add = arg1 =>{
let args = [arg1];
const fn = arg2 =>{
args.push(arg2)
return fn;
}
//当我进行判断的时候,我再调用这个方法
//重写toString方法,当进行==判断的时候会调用toString(隐式转换)
fn.toString = function(){
return args.reduce((prev,item)=>prev+item,0)
}
return fn;
}
console.log(add(1)(2) == 3,add(1)(2)(3)(4) == 10)
const add2 =(...arg1)=>{
let args = [...arg1];
const fn=(...arg2)=>{
args = [...args,...arg2];
if(args.length ===5){
return args.reduce((prev,item) => prev+item,0)
}else{
return fn;
}
}
return fn;
}
console.log(add2([1,2,3](4)(5))==15)
//如果是要输出6呢?
//add(1)(2)(3)
//new Promise()
//lazyMan
add(1)(2)(3)..toString()
add(1)(2)(3) .toString()
前端面试中的LazyMan
实现一个LazyMan,可以按照以下方式调用:\
LazyMan(“Hank”)输出:\
Hi! This is Hank!
LazyMan(“Hank”).sleep(10).eat(“dinner”)输出\
Hi! This is Hank!\
//等待10秒..\
Wake up after 10\
Eat dinner~
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出\
//等待5秒\
Wake up after 5\
Hi This is Hank!\
\
Eat supper\
以此类推
- 题目解析:看起来是链式调用并且有流程控制其中sleepFisrt要在所有函数之前执行。大致的思路是创建一个任务队列,将每一项任务(输出名字、吃饭、睡觉)都放进队列里按顺序执行。代码如下:
let LazyMan = function(name){
let _lazyMan = {
sayHi : ()=>{
queue.push(()=>{
console.log(`Hi! This is`+name)
this.next();
})
},
eat :(food)=>{
queue.push(()=>{
console.log(`Eat`+food)
this.next();
})
},
sleep:(second)=>{
queue.push(()=>{
setTimeout(()=>{
console.log(`wake up after` + second + '秒')
},second * 1000)
})
}
},
sleepFirst:function(second){
queue.unshift(()=>{
setTimeout(()=>{
console.log(`wake up before` + second +'秒')
},second * 1000)
})
}
},
next:function(){
let fn = queue.shift()
fn && fn()
}
},
let queue = [],//存放函数的任务队列
_lazyMan.sayHi();//将打招呼放在队列
setTimeout(function(){//开始执行任务队列里的函数
_lazyMan.next();
})
return _lazyMan;
}
函数式的应用与实战
函数的缓存与闭包
//假如,我有一个计算密集型的函数
//我希望对这个函数进行包装,如何让这个函数如果之前计算过了
//我就不再计算了,直接返回值。
const menorize = fn =>{
let cacheMap = {};
return function(...args){
console.log(args,"args");//[1,2]
const cacheKey = args.join(`-`);
if(cacheMap in cacheMap){
return cacheMap[chcheKey];
}else{
return cacheMap[cacheKey] = fn.apply(this||{},args);
}
}
}
function add(a,b){
console.log(a,b);//1,2
return a+ b;
} ;
let fun = menorize(add) ;
console.log(fun(1,2),"4444")//3