函数式编程
- 前置知识
- 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函数不管是否是纯函数 都会有一个语义化的名称,更便于阅读。
可以组装成复杂任务的可能性。符合模块化概念及单一职责原则。
作业:通过声明式编程方式实现 filter 及 map 达到原生的效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
Array.prototype.myMap = function(fn){
let resArr = [];
for(let i=0;i<this.length;i++){
resArr.push(fn(this[i]));
}
return resArr;
}
let users = [{
name:"张三",
age:20
},{
name:"李四",
age:21
},{
name:"王五",
age:28
}]
// let res = users.myMap(item=>item);
// console.log(res);
Array.prototype.myFilter = function(fn){
let resArr = [];
for(let i=0;i<this.length;i++){
if(fn && fn(this[i])){
resArr.push(this[i])
}
}
return resArr;
}
let res = users.myFilter(item=>item.age>20);
console.log(res);
</script>
</html>
高阶函数
高阶函数定义
- 高阶函数:以函数作为输入或者输出的函数被称为高阶函数(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是从左至右的执行。函数如下:
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; } }
ddd
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
// 编程范式 : 面向过程 面向对象
// 是一种强调以函数使用为主的软件开发风格 ,也是一种范式。
// 多范式语言 函数一等公民
// 一、纯函数:纯函数是对给定的输入返还相同输出的函数.
// f(x) = y; 1->2 2->4 3->6...
// 不是纯函数
// let factor = 3;
// const y = function (x){
// return x * factor ;
// }
// console.log(y(2));
// const y = function (x){
// let factor = 2;
// return x * factor ;
// }
// 意义:复用性 可推测性 组合成复杂功能
// test("double(2) 等于 4" ,()=>{
// expect(double(2)).toBe(4);
// })
// 高阶函数:以函数作为输入或者输出的函数被称为高阶函数(Higher-Order Function)
// function test(cb){
// cb && cb();
// }
// test(function(){
// console.log("test");
// })
// function test(){
// return function(){
// console.log("test");
// }
// }
// test()();
// 意义;抽象
// 命令式 强调“如何做” 声明式编程 : “做什么”
// let arr = [1,2,3];
// for(let i=0;i<arr.length;i++){
// console.log(arr[i]);
// }
// 声明式
// const forEach = function(arr,fn){
// for(let i=0;i<arr.length;i++){
// fn && fn(arr[i]);
// }
// }
// forEach(arr,function(item){
// console.log(item);
// });
// let arr = [true,false,true];
// const every = function(arr,fn){
// let result = true;
// for(let i=0;i<arr.length;i++){
// result = result && fn(arr[i]);
// }
// return result;
// }
// let res = every(arr,item=>item);
// console.log(res);
// 缓存特性
// const once = function(fn){
// let done = false;
// return function(){
// if(!done){
// fn();
// done = true;
// }else{
// console.log("已经执行过了");
// }
// }
// }
// function test(){
// console.log("test函数");
// }
// let myFn = once(test);
// myFn();
// myFn();
// function memorize(fn){
// let cache = [];
// return function(){
// cache.push(fn(...arguments));
// return cache;
// }
// }
// const sum = function(a,b){
// return a + b;
// }
// let mySum = memorize(sum);
// console.log( mySum(1,2));
// console.log( mySum(2,2));
// console.log( mySum(3,2));
// 柯里化 curry : 把一个多参数函数转化成一个嵌套的一元函数的过程
// function add(x,y,z,o){
// return x+y+z+o;
// }
// add(1,2,3);
// add(1)(2)(3);
//柯里化
// const curry = function(fn){
// return function(x){
// return function(y){
// return function(z){
// return fn(x,y,z);
// }
// }
// }
// }
// 通用柯里化
// x ---> x,y --->x,y,z ....
// const curry = function (fn) {
// return function curryFn(...args) {
// if (args.length < fn.length) {
// return function () {
// return curryFn(...args, ...arguments);
// }
// } else {
// return fn(...args);
// }
// }
// }
// let myAdd = curry(add);
// console.log(myAdd(1)(2)(3)(4));
// 意义;
// 参数复用
// let str1 = "abcfdsafd";
// let str2 = "fdsaljllj";
// function addPre(pre,str){
// return pre + "-" + str;
// }
// let pre = "AA";
// // console.log(addPre(pre,str1));
// // console.log(addPre(pre,str2));
// // 参数复用
// let addPreFn = curry(addPre)(pre);
// console.log(addPreFn(str1));
// console.log(addPreFn(str2));
// 延迟执行;
// function getAjax(url,method){
// let xhr;
// if(XMLHttpRequest){
// xhr = new XMLHttpRequest();
// }else{
// xhr = new ActiveXObject();
// }
// return xhr;
// }
// function getAjax(url,method){
// if(XMLHttpRequest){
// return function(){
// return new XMLHttpRequest();
// }
// }else{
// return function(){
// return new ActiveXObject();
// }
// }
// }
// let myGetAjax = curry(getAjax)("api/users");
// let xhr = myGetAjax("get");
// console.log(xhr());
// bind(this)(1)(2)....
// 组合及管道 compose pipe
// function aFn(num) {
// return num + 2;
// }
// function bFn(num) {
// return 2 * num;
// }
// 基于值来计算
// console.log( bFn(aFn(5)));
// 无值编程 (合成运算过程);
// pointFree风格:无值编程 合成运算过程 。
// 组合:从右至左执行的;
// 管道:从左至右执行的;
// const compose = function(bFn,aFn){
// return function(num){
// return bFn(aFn(num));
// }
// }
// const compose = function(...fns){
// return function(arg){
// return fns.reverse().reduce((acc,fn)=>{
// return fn(acc);
// },arg)
// }
// }
// 组合
// const compose = (...fns)=>arg=>fns.reverse().reduce((acc,fn)=>fn(acc),arg);
// 管道
// const pipe = (...fns)=>arg=>fns.reduce((acc,fn)=>fn(acc),arg);
// let myFn = compose(bFn,aFn);
// let res = myFn(5);
// console.log(res);
// const str = "大家好,我是中国人。我爱中国。我们同住地球村。";
// // function formatStr(str){
// // let res = str.match(/。/g);
// // return res.length%2===0?'偶数':'奇数';
// // }
// // 获取句号
// const getPeriod = str=>str.match(/。/g);
// // 获取长度
// const getLength = str=>str.length;
// // 判断奇偶
// const oddOrEven = num=>num%2===0?'偶数':'奇数';
// const formatStr = compose(oddOrEven,getLength,getPeriod);
// console.log(formatStr(str));
// forEach map filter some every reduce ....
// 作业:实现 myMap 和 myFilter
// let arr = [{
// name:"张三",
// age:20
// },{
// name:"李四",
// age:25
// },{
// name:"王五",
// age:28
// }]
// let res = arr.map(item=>{
// console.log(item);
// return item;
// });
// console.log(res);
// let res = arr.filter(item=>item.age>25);
// console.log(res);
</script>
</html>
redux的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="numDiv"></div>
<button>点击</button>
</body>
<script type="module">
import createStore from './createStore.js';
function Counter(state={num:0},action){
switch(action.type){
case 'ADD':
return {num:state.num+2};
break;
case 'MINUS':
return {num:state.num-1}
break;
default:
return state;
break;
}
}
let store = createStore(Counter);
console.log(store);
let btn = document.querySelector("button");
btn.onclick = function(){
store.dispatch({type:'ADD'})
}
store.subscribe(()=>{
renderDom();
})
function renderDom(){
let mydiv = document.querySelector(".numDiv");
let state = store.getState();
mydiv.innerHTML = state.num;
}
</script>
</html>
函子
容器函子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值
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函子
处理空值的函子
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 函子
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);
}
}