初识前端函数式编程

前端开发之函数式编程实践 函数表达:compose(f, g, t) => x => f(g(t(x)),进一步结合curry可以实现compose(f)(g)(t) => x => f(g(t(x))柯里化函数是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。组合compose指的是将多个函数组合成一个函数,这样一个函数的输出就可以作为另一个函数的输入,从而实现多个函数的链式调用。函数可以当做参数传递给其他函数,也可以作为函数的返回值返回(高阶函数)。 阅读详情

函数式编程

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);

地图之家总目录 地图之家总目录 cesium示例 leaflet示例 阅读详情

相关推荐

破盾实录|Vueling航空阿卡迈防护逆向:从503挑战到Cookie生成的全流程拆解

摘要:文章分享了作者在爬取西班牙伏林航空(Vueling)机票数据时,遭遇阿卡迈(Akamai)防护系统的实战经历。起初简单的请求被拦截返回503错误,分析发现阿卡迈通过混淆JS代码实施"挑战-验证"机制,需模拟真实浏览器环境才能获取有效Cookie。作者详细拆解了逆向过程:从抓包分析JS挑战流程,到破解代码混淆和控制流平坦化,最终用Python+Pyppeteer成功模拟浏览器特征,突破防护获取数据。整个过程涉及JS反混淆、环境检测绕过和Cookie生成验证等关键技术点,为类似防护系统

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1605

前端开发之函数式编程

通用的函数式编程语言,是Haskell,被函数式原教旨主义者认为是纯函数式语言。函数式编程的思想也不断影响着传统编程语言,比如Java 8开始支持lambda表达式,而函数式编程的大厦最初就是基于lambda计算构建起来的。函数式编程对于前端来说是必选项,后端则不必。 React框架的组件从很早开始就是不仅支持类式组件,也支持函数式的组件。而后React Hooks的出现,使得函数式编程思想越来越变得不可或缺。 无副作用 这是函数式编程的精髓所在。 无副作用的函数应该符合下面的特点:

薛定谔的猫-前端领域 1506

LT8641UXE_HDMI_4_TO_1_HV1_2_LT8641_

Lt8641 Datasheet

(六)前端javascript中的函数式编程技巧

函数式编程(Functional Programming),FP 是编程范式之一,我们常听说的还有。把现实世界的事物和事物之间的联系抽象到程序世界,对运算过程进行抽象。相同的输入,永远得到相同的输出,那我们就可以缓存结果,避免重复计算。slice 是纯函数,但是 concat,splice 不是。纯函数的好处就是可以缓存结果,避免重复计算。函数式编程是用来描述数据函数之间的关系。相同的输入,得到相同的输出(纯函数)lodash 库的函数都是纯函数。相同的输入始终会得到相同的输出。面相过程,面相对象编程

峰会路转的博客 1444

前端进击之路(一):函数式编程

转自:掘金 -跟兔虫函数式编程概念一、什么是函数式编程函数式编程(Functional Programming, FP),是一种编程风格,也可以认为是一种思维模式,和面向过程、面向对象...

前端达人 612

前端函数式编程

函数式编程是一种编程范式,它强调函数的无副作用性和不可变性,避免使用共享状态和可变数据,从而降低了程序出错的风险。在前端开发中,函数式编程可以使代码更加清晰、简洁和易于测试。常见的函数式编程特点包括纯函数、不可变性和高阶函数。通过使用这些特点,可以提高代码的可读性、可维护性和可重用性。

m0_72410588的博客 596

搞懂函数式编程

高阶函数是函数式编程中最为常用的技巧,高阶函数还演化出其他概念,例如React中的高阶组件,即接收一个组件作为参数或返回一个组件作为返回值的组件。例如要遍历一个数组,命令式编程的操作是通过for或while循环对数组遍历,在函数式编程中对数组的遍历可以抽象成一个forEach函数,数组可以通过该函数对数组的每个元素进行遍历,除此之外,forEach还可以抽象成map、set结构的遍历操作,相比较命令式编程,函数式编程更加简洁。通过组合一些小的高阶函数可以创建有意义的表达式,能简化很多繁琐的程序。

sanlyshi's front-end road 1066

初识函数式编程(Functional Programming,FP)

前端学习 之 函数式编程函数式编程简介面向对象编程函数式编程纯函数非函数式编程函数式编程的对比总结 函数式编程 1. 随着 React的流行,函数式编程受到越来越多的关注; 2. Vue 3也开始拥抱函数式编程; 3. 函数式编程可以抛弃 this; 4. 打包过程中可以更好的利用 tree shaking 过滤无用代码; 5. 方便测试、方便并行处理; 6. 可以帮助我们进行函数式开发的库有:lodash、underscore、ramda等。 简介 函数式编程(Functional Programm

zimeng303的博客 536

Swift函数式编程从入门到精通:新手避坑的6个实战示例

掌握Swift函数式编程核心技巧,通过6个典型应用场景解析高阶函数、闭包与不可变数据处理。涵盖实际开发中的常见误区与优化策略,提升代码简洁性与可维护性。Swift 函数 编程示例助你快速上手,值得收藏。

LiteCode的博客 613

初识react

那么显而易见了,上面的步骤能够让我们很好的对react有一个初步的了解,那在我看来的话,上面比较突出的就是函数式编程,将html便签融合在里面,称作jsx。那我们很好奇了,怎么把data里的数据展示出来,我这里用的是vue的说法,因为我们大多数都是先学vue2, 还没学的赶紧去看vue2,由浅入深。那我们知道,react是前端三大框架之一,而且很多大厂也在用,那么学习它是不得不的事情。注意这里函数式组件一定是首字母大写的,用于区别普通的html标签。那它这里的话呢用的是单花括号,vue里面用的是双花括号。

qq_43246404的博客 708

2024年最新CSS学习笔记(初识)(3),前端算法面试题

函数式编程其实是一种编程思想,它追求更细的粒度,将应用拆分成一组组极小的单元函数,组合调用操作数据流;它提倡着 纯函数 / 函数复合 / 数据不可变, 谨慎对待函数内的 状态共享 / 依赖外部 / 副作用;

2401_84619185的博客 764

初识前端以及对vscode插件的安装

一、前端 前端技术一般分为前端设计和前端开发,前端设计一般可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括基本的HTML和CSS以及JavaScript,最新的高级版本HTML5、CSS3等。 1.核心技术 HTML 指的是超文本标记语言(Hyper Text Markup Language),其基于XML标准开发的用于表示网页的标记语言,经历了多个版本的发展,已经发展到5.0版,得力于W3C建立的标准和规范。 CSS 级联样式表(Cascading Style Sheet)简称“CSS”,

weixin_51371644的博客 482

React学习一(初识React)

文章目录前言一、React 基础知识1、什么是React2、Virtual DOM3、函数式编程4、JSX 语法(1)概念(2)JSX 基本语法①XML 基本语法②元素类型③元素属性④JavaScript 属性表达式⑤表达式二、React开发环境的搭建1、基本应用(传统应用)2、脚手架开发三、React组件1、React组件的构建2、React 组件的构建方法(1)React.createClass(2)ES6 classes(3)无状态函数四、React的渲染机制五、React优势 前言 参考资料:《深

qq_44872688的博客 1651

从零开始学前端初识JavaScript --- 今天你学习了吗?(JS:Day01)

从零开始学前端:程序猿小白也可以完全掌握!—今天你学习了吗?(JS) 复习:从零开始学前端:jQuery官网 — 今天你学习了吗?(CSS:Day26) 文章目录从零开始学前端:程序猿小白也可以完全掌握!—今天你学习了吗?(JS)前言第一节课:初识JavaScript一、什么是JavaScript?二、前端三层三、JavaScript由哪几部分组成四、JavaScript代码写在哪里五、Script标签可以写在哪里六、JavaScript注释七、打印八、获取元素九、HTML事件十、定义变量 前言 上课啦上课

每一次跌倒都是为了华丽的站起来。 1677

初识函数式编程

1、map(映射)与reduce(归约)array.map(callback,[ thisObject]);//callback参数(注意,callback需要return,否则数组每一项都会被映射为undefined) [].map(function(value, index, array) { // ... }); //我们用ES3实现 var map = Array.prototyp

qq_34893429的博客 268

2024年前端最全JavaScript-初识ajax、ajax封装、及json简单实战案例(下)(1),阿里面试官级别

函数式编程其实是一种编程思想,它追求更细的粒度,将应用拆分成一组组极小的单元函数,组合调用操作数据流;它提倡着 纯函数 / 函数复合 / 数据不可变, 谨慎对待函数内的 状态共享 / 依赖外部 / 副作用;

2401_84433974的博客 279

编程中文手册_2021 前端开发最新学习手册

目录HTMLCSSJavaScript前端框架前端工程化服务端渲染 SSR静态站点生成器 SSGTypeScriptJS 去做服务器端桌面应用程序 Electron移动端混合开发前端架构 JAMStackWebAssembly1. HTMLDOCTYPEHTML, XHTML, XML 差异性HTML5 新特性 及 语义化标签meta, img, script 等标签及其标签属性有兴趣可以了解...

weixin_30983209的博客 723

初识Nest

了解前端容易上手的服务端语言nestjs

天界程序员的博客 451

仓颉编程语言 -- 初识(一)

仓颉编程语言 -- 初识(一)

chinusyan的专栏 2979

JavaScript-throw、try,头条前端面试,前端校招面试题及解析大全

函数式编程其实是一种编程思想,它追求更细的粒度,将应用拆分成一组组极小的单元函数,组合调用操作数据流;它提倡着 纯函数 / 函数复合 / 数据不可变, 谨慎对待函数内的 状态共享 / 依赖外部 / 副作用;

m0_60229361的博客 733
上一篇: 玩转vue面试题系列3 结合源码分析面试题
下一篇: 前端面经 之html5相关面经收集与总结
尤雨东
博客等级 码龄7年 381粉丝 152原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

尤雨东

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

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

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

打赏作者

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

抵扣说明:

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

余额充值