初识前端函数式编程

函数式编程

function add(a,b){
return a + b;
}
console.log(add(1,2));

函数,其实是数学上的一种概念: y = f(x)

函数式编程,本质上就是一直映射关系。

参数是输入,返回值是输出,相同的输入一般会产生相同的输出。

First-class Function 头等函数

当一门编程语言的函数可以被当作变量一样用时,则称这门语言拥有头等函数。例如,在这门语言中,函数可以被当作参数传递给其他函数,可以作为另一个函数的返回值,还可以被赋值给一个变量。

函数可以赋值给一个变量
function add(a,b){
	return a + b;
}
const add2 = add;
函数可以作为参数
function exec(fn,a,b){
	return fn(a,b);
}
exec(add,1,2);
闭包

… 没什么好说的。

纯函数

什么是纯函数:

  1. 相同的输入,有相同的输出。
  2. 执行过程没有副作用。

优点:

  1. 纯函数可缓存
  2. 可测试
纯函数的缓存
import _ from "lodash";

// 纯函数的优点:
// 1. 可缓存 -> 先使用lodash库进行测试
let count = 0;
const add = (a, b) => {
  console.log(`这是add函数第${++count}次执行`);
  return a + b;
};

const resolver = (...args) => JSON.stringify(args);
const memorizeAdd = _.memoize(add, resolver);
console.log(memorizeAdd(1,2));
console.log(memorizeAdd(1,2));
console.log(memorizeAdd(1,2));
console.log(memorizeAdd(1,2));

image-20220427141609649

缓存函数的实现
// 纯函数的优点:
// 1. 可缓存 -> 先使用lodash库进行测试
let count = 0;
const add = (a, b) => {
  console.log(`这是add函数第${++count}次执行`);
  return a + b;
};

// memoize方法的实现
const memoize = (func, resolver) => {
  // 缓存对象 存放参数和缓存结果的对应关系
  const cache = {};
  return (...args) => {
    const key = resolver(...args);
    if (typeof cache[key] !== "undefined") {
      return cache[key];
    }
    return (cache[key] = func(...args));
  };
};
// 进行参数序列化的 就是作为缓存的key
const resolver = (...args) => JSON.stringify(args);
const memorizeAdd = memoize(add, resolver);
console.log(memorizeAdd(1, 2));
console.log(memorizeAdd(1, 2));
console.log(memorizeAdd(1, 2));
console.log(memorizeAdd(1, 2));
柯里化
const sum = (a, b, c) => a + b + c;
const curry = (func) => {
  const curried = (...args) => {
    if (args.length < func.length)
      return (...params) => curried(...args, ...params);
    else return func(...args);
  };
  return curried;
};

const currySum = curry(sum);
// 偏柯里化 一次可以 传入多个参数
console.log(currySum(1, 2)(3));
// 柯里化 一次调用只能给一个参数
console.log(currySum(1)(2)(3));


// 无限柯里化
const curred = (func) => {
  let params = [];
  const curried = (...args) => {
    params = [...params,...args];
    return curried;
  };
  curried["toString"] = function () {
    return func(...params);
  };
  return curried;
};
const sums = curred((...args) => args.reduce((prev, next) => prev + next, 0));
console.log(sums(1)(2)(3).toString());


const curring = () => {
  let params = [];
  const curried = (...args) => {
    params = params.concat(args);
    return curried;
  };
  curried["toString"] = function () {
    return params.reduce((prev, next) => prev + next, 0);
  };
  return curried;
};
console.log(curring()(1)(2).toString())
组合
let str = "a";
const add1 = (str) => str + "1";
const add2 = (str) => str + "2";
const add3 = (str) => str + "3";
const add4 = (str) => str + "4";

// 把多个函数组合成一个函数 
// 第一个函数的执行结果返回给第二个函数执行...
const flow = (...funcs) => {
  return (arg) => funcs.reduceRight((prev, next) => next(prev), arg);
};
//  add1(str) -> add2(add1(str)) -> add3(add2(add1(str)))
console.log(flow(add4, add3, add2, add1)(str)); // a1234

const compose = (...funcs) => {
  return funcs.reduce((prev, next) =>(args)=>next(prev(...args)));
};
console.log(compose(add1,add2,add3,add4)(str))// a1234
pointfree

把数据处理的过程,先定义成一种与参数无关的合成运算,就叫做pointfree

pointed 是有指向的,我有一百万我会做啥

pointfree,是假如我有一百万,我会怎么花(现在没有钱)

容器

如果一个对象内部能够存储一个值,我们就称为一个容器。

class Container {
  constructor(value) {
    this.value = value;
  }
}
const c1 = new Container(1);
console.log(c1.value)
pointed 容器

如果一个容器内有of方法,我们就称为有指向的容器(是静态方法)

我们是函数式编程,所以我们要尽量避免new对象

class PointedContainer {
  constructor(value) {
    this.value = value;
  }
  // 写一个类似静态过程方法的of方法,用来返回生成我们想要的实例
  static of(value) {
    return new PointedContainer(value);
  }
}
console.log(PointedContainer.of(1));
console.log(
  Functor.of(1)
    .map((value) => value + 2)
    .map((value) => value + 3)
    .map((value) => value + 4)
    .map((value) => value + 5)
);
函子

如果有map方法,可称为Functor函子

  • 函子 会有一个静态的of方法,用来生成实例
  • 函子内部也会保存一个值 value
  • 函子提供了map方法,接入各种运算函数,从而引发值的变化
/**
 * 函子有点像函数
 * 函数其实也是一个映射关系,可以把参数映射返回值
 * map也是映射的意思 可以把老的实例映射为一个新的实例
 * 也可以说把一个老的值,映射为一个新的值
 */
class Functor {
  constructor(value) {
    this.value = value;
  }
  static of(value) {
    return new Functor(value);
  }
  // map方法 是接收一个函数 返回值还是一个同类型的对象,它就是函子容器
  map(fn) {
    return new Functor(fn(this.value));
  }
}
// 1+2 
console.log(Functor.of(1).map((value) => value + 2));
maybe

容器内部的值可能是一个空值,而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。

Maybe函子可以过滤空值,能过滤空值的函子被称为Maybe函子

在出现空值的时候,不会出错。

class Maybe {
  constructor(value) {
    this.value = value;
  }
  static of(value) {
    return new Maybe(value);
  }
  // 过滤空值 null | undefined
  map(fn) {
    return this.value != null ? new Maybe(fn(this.value)) : this;
  }
}
console.log(Maybe.of(null).map((x) => x.toString()));
Either函子

Either函子内部有两个值,左值left和右值right,右值是正常情况下使用的值,左值是右值不存在时使用的默认值。

常用来设置默认值和处理异常。

/**
 * Either 函子 内部有两个值 left 和 right
 * 左值只会在右值不存在的情况下起作用
 * 可以用来处理默认值问题
 */
class Either {
  constructor(left, right) {
    this.left = left;
    this.right = right;
  }
  static of(left, right) {
    return new Either(left, right);
  }
  map(fn) {
    return this.right != null
      ? Either.of(this.left, fn(this.right))
      : Either.of(fn(this.left), this.right);
  }
  get value() {
    return this.right != null ? this.right : this.left;
  }
}
const response = { name: "张三", gender: null };
let either = Either.of("男", response.gender).map((x) => `性别:${x}`);
console.log(either.value);
Ap函子
  • ap(applicative)的函子拥有ap方法

  • ap方法可以让一个函子内部的函数使用另一个函数的值进行计算

  • ap方法的参数不是函数,而是另一个函子

class Ap {
  constructor(value) {
    this.value = value;
  }
  static of(value) {
    return new Ap(value);
  }
  // 参数是一个函子
  ap(functor) {
    // 这里value当时保存的是个函数
    return Ap.of(this.value(functor.value));
  }
}
const a = Ap.of((x) => x + 1);
const b = Ap.of(2);
console.log(a.ap(b))
Monad函子
  • 函子的值也可以是函子,这样会出现多层函子嵌套的情况
  • monad(单子【不可分割的实体】)函子的作用是,总是返回一个单层的函子
  • 它又一个flatMap方法,与map方法作用相同,唯一的区别是如果生成了嵌套函子,它回去取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。
/**
 * Monad 单子 不能分割的实体 没有嵌套
 */
class Monad {
  constructor(value) {
    this.value = value;
  }
  static of(value) {
    return new Monad(value);
  }
  // fn函数返回值是一个函子,那么会出现一个递归嵌套的情况,计算和取值都麻烦
  map(fn) {
    return new Monad(fn(this.value));
  }
  // 本来是函子的值是一个函子 把值取出来返回
  flatMap(fn){
    return this.map(fn).join()
  }
  join(){
    return this.value
  }
}
const fc = Monad.of("a")
  .map((x) => Monad.of(x + 1))
  .map((x) => Monad.of(x.value + 2))
  .map((x) => Monad.of(x.value + 3))
console.log(fc,fc.value)

const m = Monad.of("a")
.flatMap((x) => Monad.of(x + 1))
.flatMap((x) => Monad.of(x + 2))
.flatMap((x) => Monad.of(x + 3))
console.log(m)
IO函子与副作用
  • 副作用就是程序和外部世界的交互,比如读取文件和调用接口
  • 由于外部世界不可控,包含副作用的逻辑往往不可预测
  • 函数式编程提倡把副作用分离出来,让没有副作用的纯逻辑放在一起原理包含副作用的逻辑,这就需要IO Monad
  • IO就是Input/output,副作用无非是对外部世界的Input和OutPut
  • IO函子通过推迟执行的方式来实现对副作用的管理和隔离
const localStorage = {
  getItem(key) {
    switch (key) {
      case "data":
        return `{"code":"0","userId":1}`;
      case 1:
        return `{"userId":1,"name":"张三","age":22}`;
    }
  },
};

const compose = (...fns) => {
  if (fns.length === 1) return fns[0];
  return fns.reduce(
    (prev, next) =>
      (...args) =>
        prev(next(...args))
  );
};

// 函数式的编程思维 把纯的逻辑收集封闭起来,然后把不纯的副作用操作交给用户处理
class IOMonad {
  constructor(value) {
    this.value = value;
  }
  map(fn) {
    // compose 把this.value和fn组合成一个新的函数
    return new IOMonad(compose(fn, this.value));
  }
  flatMap(fn) {
    //  x.value() 拿到前面组合函数的返回值了
    return new IOMonad(compose(x=>x.value(),fn, this.value));
  }
  start(callback) {
    typeof callback === "function" ? callback(this.value()) : this.value();
  }
}
// 输入用副作用
const readByKey = (key) => new IOMonad(() => localStorage.getItem(key));
// 纯函数
const parseJSON = (str) => JSON.parse(str);
// 用副作用 输出
const write = console.log;

readByKey("data")
  .map(parseJSON) // 在这里之前都没有发生副作用
  // 函数本身是纯的,但是函数IO执行不是纯的(start之前只是收集函数进行组合而已 不存在副作用)
  // start函数调用之前不能执行
  .start(write);

readByKey("data")
  .map(parseJSON)
  .map((x) => x.userId) // 1
  .flatMap(readByKey) // 实现链式调用
  .map(parseJSON)
  .start(write);

Task函子
  • task函子通过类似promise的resolve的风格来声明一个异步流程
  • FP中除了容器container,也可以用上下文context来称呼包裹了一个值的结构
  • Promise的任务是立刻执行的,而task是在调用的时候才开始执行的
const get = (url) => {
  if (url === "data") return Promise.resolve({ code: 0, userId: 1 });
  else if (url === 1) return Promise.resolve({ userId: 1, name: "张三" });
};

/**
 * 函数式编程 new对象
 * static of 内部也是需要new对象
 * 类和new对象都不是必须的
 */
const Task = (executor) => ({
  executor,
  map(fn) {
    console.log(Task((resolve) => executor((x) => resolve(fn(x)))));
    return Task((resolve) => executor((x) => resolve(fn(x))));
  },
  flapMap: (fn) => Task((resolve) => executor((x) => fn(x).executor(resolve))),
});
const request = (url) => Task((resolve) => get(url).then(resolve));
request("data") // { executor:(resolve) => get(url).then(resolve),map(fn){} }
  .map((x) => x.userId) // {map, executor: (resolve)=>  }
  .flapMap(request)
  .map((x) => x.name)
  .executor(console.log);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尤雨东

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值