函数式编程
- 前置知识
- js 基础
- js面向对象
函数式编程含义
-
函数式编程是一种强调以函数使用为主的软件开发风格 ,也是一种范式。
-
某些函数式编程语言Haskell、lisp、Scheme等。
js中函数式编程
-
数学中函数
f(x) = y;
-
js中的函数
jslet factor = 3; let totalNum = num=>factor*num; console.log( totalNum(3) );
-
对比两种函数,基于数学函数 来修复 js函数
jslet totalNum = (num,factor)=>factor*num; console.log( totalNum(3,3) );
-
js是多范式编程语言,但是函数作为一等公民,函数式编程具有天然优势。
###函数式编程中涉及到的概念
####纯函数
-
函数式编程基于纯函数
-
纯函数是对给定的输入返还相同输出的函数;例如
jslet double = value=>value*2;
-
-
纯函数意义
-
纯函数可以产生可测试的代码
-
不依赖外部环境计算,不会产生副作用,提高函数的复用性。
jstest('double(2) 等于 4', () => { expect(double(2)).toBe(4); })
-
-
可读性更强 ,js函数不管是否是纯函数 都会有一个语义化的名称,更便于阅读。
-
可以组装成复杂任务的可能性。符合模块化概念及单一职责原则。
-
高阶函数
-
高阶函数定义
- 高阶函数:以函数作为输入或者输出的函数被称为高阶函数(Higher-Order Function)。
-
高阶函数的抽象
-
一般高阶函数用于抽象通用问题,简而言之,高阶函数就是定义抽象。
-
命令式循环(注重"如何"做,注重过程);
jslet arr = [1,2,3]; for(let i=0;i<arr.length;i++){ console.log(arr[i]); }
-
通过高阶函数抽象过程,声明式编程(注重做"什么",注重结果);
jsconst forEach = function(arr,fn){ for(let i=0;i<arr.length;i++){ fn(arr[i]); } } let arr = [1,2,3]; forEach(arr,(item)=>{ console.log(item); })
上面通过高阶函数 "forEach"来抽象循环"如何"做的逻辑,直接关注 做"什么"
-
-
-
高阶函数的缓存特性
-
主要是利用函数的闭包
- once 高阶函数
jsconst once = (fn)=>{ let done = false; return function(){ if(!done){ fn.apply(this,fn); }else{ console.log("this fn is already execute"); } done = true; } } function test(){ console.log("test..."); } let myfn = once(test); myfn(); myfn();
-
柯里化
-
什么是柯里化?
-
柯里化是把一个多参数函数转化成一个嵌套的一元函数的过程;
-
如下二元函数
jslet fn = (x,y)=>x+y;
-
柯里化函数
jsconst curry = function(fn){ return function(x){ return function(y){ return fn(x,y); } } } let myfn = curry(fn); console.log( myfn(1)(2) );
-
-
-
多参数函数柯里化
js// 多参数柯里化; const curry = function(fn){ return function curriedFn(...args){ if(args.length<fn.length){ return function(){ return curriedFn(...args.concat([...arguments])); } } return fn(...args); } } const fn = (x,y,z,a)=>x+y+z+a; const myfn = curry(fn); // console.log(myfn(1)(2)); console.log(myfn(1)(2)(3)(1));
-
柯里化意义
- 让纯函数更"纯",每次接受一个参数,松散解耦
- 某些语言及特定环境下只能接受一个参数
- 惰性执行
组合(composition)和管道(pipe)
####组合(composition)
-
组合函数:无需创建新的函数,通过基础函数解决眼前问题。
-
组合高阶函数 map 及 filter;
-
map高阶函数
-
组合map及filter来使用
-
-
compose组合
可以封装组合函数来实现函数执行
- 2个函数组合
jsfunction afn(a){ return a*2; } function bfn(b){ return b*3; } const compose = (a,b)=>c=>a(b(c)); let myfn = compose(afn,bfn); console.log( myfn(2));
-
多函数组合
jsconst compose = (...fns)=>val=>fns.reverse().reduce((acc,fn)=>fn(acc),val);
-
管道(pipe)
compose 执行是从右到左,pipe是从左至右的执行。函数如下:
js
const pipe = (...fns)=>val=>fns.reduce((acc,fn)=>fn(acc),val);
管道、组合 取舍 :管道及组合最大区别在于执行顺序的不同,数据流向不同,达到目的是类似的。所以无优 劣之分,保持团队风格统一就好了。
组合及管道的意义 把很多小函数组合起来完成更复杂的逻辑。
Pointfree 编程风格
概念:不适用处理的值,只合成运算过程,也就是无值风格。
-
获取所有的句号
jsconst sentenceNum = str=>str.match(/。/g); let str = "大家好,我是中国人。我爱中国。我们同住地球村。"; console.log(sentenceNum(str));
-
统计长度
jsconst countFn = arr=>arr.length;
-
判断奇偶
jsconst oddOrEven = num=>num%2===0?"偶数":"奇数";
-
pointfree风格组合函数使用:找到句号统计长度最后判断奇偶数
jslet str = "大家好,我是中国人。我爱中国。我们同住地球村。"; const myfn = compose(oddOrEven,countFn,sentenceNum); console.log(myfn(str));
js函数式编程库
-
lodash.js 、ramda.js 、Underscore.js
-
通过 ramda.js实现数据筛选功能;
##函数式编程在redux 中应用
-
Redux 整体是通过函数式 编程思维实现的;
-
createStore 简单实现
jsconst createStore = (reducer, initialState) => { const store = {}; let currentState = initialState; const listeners = []; const getState = () => currentState; const subscribe = (listener) => { listeners.push(listener); }; const dispatch = (action) => { // store.state = deepFreeze(reducer(store.state, action) ); currentState = reducer(currentState, action); listeners.forEach(listener => listener()); }; return { getState, subscribe, dispatch }; }; export {createStore};
-
状态管理调用
jsimport {createStore} from "../MyRedux.js"; // import { createStore } from 'redux'; function Counter(state={count:0},action) { switch (action.type) { case "ADD_COUNT": return {count:state.count + 1} break; case "REDUCE_COUNT": return {count:state.count - 1} break; default: return state; break; } } var store = createStore(Counter); btn.onclick = function(){ // console.log(store); store.dispatch({type:"ADD_COUNT"}); } store.subscribe(() => { // console.log(store) renderDom() }) function renderDom(){ let mydiv = document.querySelector(".countDiv"); if(typeof store.getState() !== "undefined"){ mydiv.innerHTML = store.getState().count; } }
函子
容器函子Functor
函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。
-
定义自己的Functor。
jsclass Container{ constructor(value){ this._value = value; } static of(val){ return new Container(val); } } let res = Container.of(1); console.log(res);
-
通过map修改Functor值
js
class Container{
constructor(value){
this._value = value;
}
static of(val){
return new Container(val);
}
map(fn){
return Container.of(fn(this._value));
}
}
let res = Container.of(1);
// console.log(res);
let reslut = res.map(item=>{
// console.log(item);
return item+3
}).map(item=>{
return "最终结果是" + item;
})
MeBe函子
处理空值的函子
js
class Mabe{
constructor(val){
this._value = val;
}
static of(val){
return new Mabe(val);
}
isNoThing(){
return (this._value===null || this._value=== undefined);
}
map(fn){
return this.isNoThing()?Mabe.of(null):Mabe.of(fn(this._value));
}
}
Either 函子
js
class Either extends Functor{
constructor(left,rigth){
super();
this.left = left;
this.right = rigth;
}
map(f){
return this.right?Either.of(this.left, f(this.right)) :
Either.of(f(this.left), this.right);
}
static of(left, right){
return new Either(left, right);
}
}