谈谈对函数式编程的理解及rxjs的使用

背景

函数式编程可以说是非常古老的编程方式,但是近几年变成了一个非常热门的话题。不管是Google力推的Go、学术派的Scala与Haskell,还是Lisp的新语言Clojure,这些新的函数式编程语言越来越受到人们的关注。函数式编程思想对前端的影响很大,Angular、React、Vue等热门框架一直在不断通过该思想来解决问题。

函数式编程作为一种高阶编程范式,更接近于数学和代数的一种编程范式,与面向对象的开发理念和思维模式截然不同,深入理解这种差异性,是程序员进阶的必经之路。

编程范式

编程范式(Programming Paradigm)是编程语言领域的模式风格,体现了开发者设计编程语言时的考量,也影响着程序员使用相应语言进行编程设计的风格。大体分为两大类,具体内容如下图所示:

函数式概念与思维

函数式编程(Functional Programming)是基于λ演算(Lambda Calculus)的一种语言模式,它的实现基于λ演算和更具体的α-等价、β-归约等设定 。这是一个较官方的解释,大家不要被这种概念吓到,很有可能你已经在日常开发中使用了大量的函数式编程概念和工具。如越来越函数式的ES6,新的规范有非常多的新特性,其中不少借鉴其他函数式语言的特性,给JavaScript语言添加了不少函数式的新特性。箭头函数就是ES6发布的一个新特性,箭头函数也被叫做肥箭头(Fat Arrow),大致是借鉴自CoffeeScript或者Scala语言。箭头函数是提供词法作用域的匿名函数。

函数式编程思维的目标:程序执行时,应该把程序对结果以外的数据的影响控制到最小

函数式编程的特点

  1. 声明式(Declarative)

  2. 纯函数(Pure Function)

  • 函数的执行过程完全由输入参数决定,不会受除参数之外的任何数据影响。

  • 函数不会修改任何外部状态,比如修改全局变量或传入的参数对象。

  1. 数据不可变性(Immutability)

    当我们需要数据状态发生改变时,保持原有数据不变,产生一个新的数据来体现这种变化。不可改变的数据就是Immutable数据,一旦产生,可以肯定它的值永远不会变,这非常有利于代码的理解。

下面用一段对比代码解释命令式编程与函数式编程

// 计算传入数据乘以2
​
// 命令式编程
function double(arr) {const results = []for (let i = 0; i < arr.length; i++){results.push(arr[i] * 2)}return results
}
​
console.log(double([1, 2, 3]));// [2, 4, 6]
​
// 函数式编程
function double(arr) {return arr.map(item => item * 2);
}
​
const oneArray = [1, 2, 3];
const anotherArray = double(oneArray);
​
console.log(oneArray); // [1, 2, 3]
console.log(anotherArray);// [2, 4, 6]

函数是一等公民

数字在JavaScript里就是一等公民,同样作为一等公民的函数就会拥有类似数字的性质。

  1. 函数与数字一样可以存储为变量

    let one = function() { return 1 };
  2. 函数与数字一样可以存储为数组的一个元素

    let ones = [1, function() { return 1 }];
  3. 函数与数字一样可以被传递给另一个函数

function numAdd(n, f) { return n + f()};
numAdd(1, function() { return 1}); // 2

       4. 函数与数字一样可以被另一个函数返回

return 1;
return function() { return 1 };

最后两点其实就是“高阶”函数的定义;一个高阶函数应该可以至少执行一项,以一个函数作为参数或者返回一个函数作为结果。

高阶函数(High Order Function)

高阶函数,通俗来说,就是以其他函数为参数的函数,返回其他函数的函数。我们称函数的嵌套高阶调用为高阶函数,高阶函数可以说是编程语言便捷践行函数式的基础。比如在React中我们会遇到的高阶组件HOC。

以数字添加千分位符号为demo的代码如下:

const addThousandSeprator = (strOrNum) => {return parseFloat(strOrNum).toString().split('.').map((x,idx) => {if(!idx) {return x.split('').reverse().map((xx,idxx) => (idxx && !(idxx % 3)) ? (xx + ',') : xx ).reverse().join('')} else {return x;}}).join('.')
}

高阶函数应用之柯里化(Currying)

柯里化函数为每一个逻辑参数返回一个新的函数,会逐渐返回已配置的函数,直到所有的参数用完。

function curry(fun) {return function(arg) {return fun(arg)}
}
​
const arr = ['1', '2', '3', '4'].map(curry(parseInt));
console.log(arr) // [ 1, 2, 3, 4 ]

使用柯里化比较容易产生流利的函数式API。在Haskell编程语言中,函数式默认柯里化。但在JavaScript中,函数式API的设计必须利用柯里化,而且必须文档化。

递归

程序调用自身的编程技巧称为递归( recursion)。递归作为一种算法在程序设计语言中广泛应用。 递归是一种解决过程堆叠的方法,在运行时承担了更多的工作。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。

说起递归,不得不谈起尾递归。早期的浏览器引擎是不支持尾递归,所以当我们计算经典的斐波那契数列或进行其他递归操作时,可能会触发堆栈调用超限的提醒。如果每次递归尾部返回的内容都是一个待计算的表达式,那么运行时的内存栈中会一直压入等待计算的变量和环境,这就是产生超限的根本原因。而如果我们使用新的递归方法,若运行环境支持优化,则立即释放被替换的函数负载。

// 递归:将外层调用保存在内存堆栈中
const factorialFn = (n) =>  {if (n <= 1) {return 1;} else {return n + factorialFn(n - 1);}
}
console.log('factorialFn:  ', factorialFn(30))// 465
​
// 返回函数调用;尾递归优化
const factorialFun = (n, acc) => {if(n <= 1) {return acc;} else {return factorialFun(n - 1, n + acc)}
}
​
console.log('factorialFun: ', factorialFun(30, 1))// 465

基于流的编程

在前端领域中,「流」的经典代表之一「RxJS」。

在Rx官网ReactiveX 上,有一段介绍文字:

An API for asynchronous programming with observable streams.

翻译过来就是:Rx是一套通过可监听流来做异步编程的API。老实说,这句描述并没有把概念解释清楚,所以在下面我们就用普通的语言来解释Rx。

RxJS初认识

RxJS是Reactive Extension模式的JavaScript语言实现

RxJS是一个使用可观察序列组成异步和基于事件的程序库。它提供了一种核心类型,Observable,广播类型(Observer,Schedulers,Subjects)和操作符(map,filter,reduce等),允许将异步事件作为集合处理。

RxJS的运行就是Observable和Observer之间的互动游戏。

RxJS中的数据流就是Observable对象,Observable实现了两种设计模式:观察者模式(Observer Pattern)、迭代器模式(Iterator Pattern)

Observable和Observer的关系是观察者模式和迭代器模式的结合,通过Observable对象的subscribe函数,可以让一个Observer对象订阅某个Observable对象的推送内容,可以通过unsubscribe函数退订内容。

RxJS核心概念

Observable:可观察者对象,表示可以调用的未来值或事件集合的方法。

Observer: 观察者,是一组回调函数,处理Observable提供的值。


/*** Observable对象(source$)就是一个发布者,通过Observable对象的subscribe函数,把发布者和观察者连接起来* 扮演观察者的是console.log,不管传入什么“事件”,它只管把“事件”输出到console上*/
const source$ = of(1, 2, 3);  // 发布者
source$.subscribe(console.log); // 观察者

这段代码输出结果如下:

Subscription:订阅关系,表示Observable执行,主要用于取消执行。

import {Observable} from 'rxjs/Observable';const onSubscribe = observer => {let number = 1;const handle = setInterval(() => {console.log(`onSubscirbe: ${number}`)observer.next(number++);}, 1000);return {unsubscribe: () => {clearInterval(handle);}};
};const source$ = new Observable(onSubscribe);
const subscription = source$.subscribe(item => console.log(`第${item}次调用`));setTimeout(() => {subscription.unsubscribe();
}, 5500);

这段代码输出结果如下:

该行代码被注释后 clearInterval(handle),代码输入结果如下:

当unsubscribe函数中的clearInterval被注释掉后,也就是setInterval不被打断,setInterval的函数参数中输出当前number,修改之后的程序会不断的输出 onSubscirbe: n。

由此可见,Observable产生的事件,只有Observer通过subscribe订阅之后才会收到,在unsubscribe之后就不会再收到

Operators:操作符,纯粹的函数,一个操作符是返回一个Observable对象的函数。

说起操作符,不得不说的就是弹珠图,弹珠图可以通过动画很直白的向我们展示操作过程,动态:​ https://reactive.how/rxjs/ , 静态:RxMarbles: Interactive diagrams of Rx Observables。

在所有操作符中最容易理解的可能就是mapfilter,因为JavaScript的数组对象有两个同名的函数map和filter。

JavaScript写法:

const source = [1,2,3,4,5,6];
const result = source.filer(x => x % 2 === 0).map(x => x * 2);
console.log(result);

RxJS写法:

const result$ = of(1,2,3,4,5,6).filter(x => x % 2 === 0).map(x => x * 2);
result$.subscribe(console.log);

按功能分类,大致可以分为9大类:

  • 创建类(creation)

  • 转化类(transformation)

  • 过滤类(filtering)

  • 合并类(conbination)

  • 多播类(multicasting)

  • 错误处理类(error Handling)

  • 辅助工作类(untility)

  • 条件分支类(conditional & boolean)

  • 数据和合计类(mathmatical & aggregate)

Subject:主题,相当于EventEmitter,将值或事件广播到多个Observer的唯一方法。

import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/take';const tick$ = Observable.interval(1000).take(3);
const subject = new Subject();
tick$.subscribe(subject);subject.subscribe(value => console.log('observer 1: ' + value));
setTimeout(() => {subject.subscribe(value => console.log('observer 2: ' + value));
}, 1500);

这段代码的执行结果如下:

以上代码可以看出,Subject兼具Observable和Observer的性质,就像有两副面孔,可以左右逢源。

日常常用场景如浏览器中鼠标的移动事件、点击事件,浏览器的滚动事件,来自WebSocket的推送消息,还有Node.js支持的EventEmitter对象消息,及微服务系统中主应用与各个子应用之间的通信等。

Scheduler:控制并发的集中调度器,使我们能够协调发生在setTimeout或其他的事件。

Scheduler实例:

undefined/null:也就是不指定Scheduler,代表同步执行的Scheduler。

asap:尽快执行的Scheduler。

async:利用setInterval实现的Scheduler,用于基于时间吐出数据的场景。

queue:利用队列实现的Scheduler,用于迭代一个大的集合的场景。

animationFrame:用于动画场景的S cheduler。

RxJS默认选择Scheduler的原则是:尽量减少并发运行。所以,对于range,就选择undefined,指的是同步执行的Scheduler;对于很大的数据,就选择queue;对于时间相关的操作符比如interval,就选择async。

import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/range';
import {asap} from 'rxjs/scheduler/asap';const source$ = Observable.range(1, 3, asap);console.log('before subscribe');
source$.subscribe(value => console.log('data: ', value),error => console.log('error: ', error),() => console.log('complete')
);
console.log('after subscribe');

这段代码的执行结果如下:

函数式在前端的积极作用

web开发时,我们会在服务端管理大量的系统状态和系统数据,可以看到随着前端工作流逐渐增多,事件和远程状态响应都会变得错综复杂。对于查看一个多于10个页面或组件复杂的项目代码时,我们会发现相比于后端,很难通过前端代码读懂整个业务链路。如果我们将核心代码更换成较为合理的函数式逻辑,或者使用函数式工具和规范对已有逻辑进行归纳,就可以明显提高代码的可读性和代码运行时的可调试性,这也是对历史代码进行升级、改造的方法之一。

前端函数式的初衷是我们希望能更好、更快、更强地解决开发过程中遇到的问题。与其等待后续的治理,不如在日常开发中进行合理的规划,养成良好的开发习惯。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/458549.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言基础题(大合集2)

1. 时间转换 给定秒数 --> 输出秒数 转化成 时/分/秒 //时间转换 //给定秒数 --> 转换成 小时/分/秒 int main() {//输入int seconds 0;int h 0;//小时int m 0;//分钟int s 0;//秒scanf("%d", &seconds);//计算h seconds / 60 / 60;m seconds / 60…

详解varint,zigzag编码, 以及在Go标准库中的实现

文章目录 为啥需要varint编码为啥需要zigzag编码varint编码解码 zigzag编码解码 局限性 为啥需要varint编码 当我们用定长数字类型int32来表示整数时&#xff0c;为了传输一个整数1&#xff0c;我们需要传输00000000 00000000 00000000 00000001 32 个 bits&#xff0c;而有价…

【C++】STL初识

【C】STL初识 文章目录 【C】STL初识前言一、STL基本概念二、STL六大组件简介三、STL三大组件四、初识STL总结 前言 本篇文章将讲到STL基本概念&#xff0c;STL六大组件简介&#xff0c;STL三大组件&#xff0c;初识STL。 一、STL基本概念 STL(Standard Template Library,标准…

QT建立工程时出现了:Reading Project

QT建立工程时出现了&#xff1a;Reading Project 打开建立的工程发现&#xff0c;缺少build文件 从别的工程中复制一个build&#xff0c;点击.pro就可以打开了

【CSS3】css开篇基础(4)

1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; Hello, Hello~ 亲爱的朋友们&#x1f44b;&#x1f44b;&#xff0c;这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章&#xff0c;请别吝啬你的点赞❤️❤️和收藏&#x1f4d6;&#x1f4d6;。如果你对我的…

Spring Boot实现的动态化酒店住宿管理系统

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理酒店客房管理系统的相关信息成为必然。开发…

图文详解ChatGPT-o1完成论文写作的全流程

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 本月中旬OpenAI发布了OpenAI o1系列新的AI模型。 据OpenAI介绍&#xff0c;这些模型旨在花更多时间思考后再做出反应&#xff0c;就像人一样。通过训练&#xff0c;它们学会改进思维过…

深度学习(六)CNN:图像处理的强大工具(6/10)

一、CNN 的概述 卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;CNN&#xff09;是深度学习的代表算法之一&#xff0c;在深度学习中占据着重要地位。 CNN 的发展历程可追溯至 20 世纪 80 至 90 年代&#xff0c;时间延迟网络和 LeNet - 5 是最早出现的卷…

conda虚拟环境中安装cuda方法、遇到的问题

conda虚拟环境中安装cuda方法、遇到的问题 文章目录 conda虚拟环境中安装cuda方法、遇到的问题conda虚拟环境中安装cudacuda.h和cuda_runtime.hpytorch运行时的CUDA版本其他问题检查包冲突nvcc -V和nvidia-smi显示的版本不一致cuda路径 conda虚拟环境中安装cuda 参考文章&…

【AIGC】从CoT到BoT:AGI推理能力提升24%的技术变革如何驱动ChatGPT未来发展

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;迈向AGI的新跨越&#x1f4af;BoT与CoT的技术对比技术原理差异推理性能提升应用范围和通用性从错误中学习的能力总结 &#x1f4af;BoT的工作流程和机制初始化过程生成推…

layaair获取组件里的脚本

获取脚本用getComponents方法&#xff0c;但是这个方法里的参数不是脚本的名称。而是组件类型。如果你需要获取脚本&#xff0c;则类型为Laya.Script。挺坑的。我在官网找都没找到这个是这么用的。我猜测的。没想到试了一下成功了。 property(Laya.Node)public img1: Laya.Node…

碰一碰支付系统搭建怎么做?头部公司源码大测评!

随着碰一碰支付dai li骗局的曝光&#xff0c;越来越多的人开始选择将目光转向碰一碰支付系统搭建这一入局方式&#xff0c;连带着与之相关的多个话题&#xff0c;如碰一碰支付系统搭建怎么做等也成为了当前的一大热点。 毕竟&#xff0c;相较于dai li 模式的与第三方公司合作、…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-26

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-26 前言 本期相关论文可以从“下载” 资源中获取&#xff0c;如果有感兴趣的问题&#xff0c;欢迎交流探讨&#xff01; 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-26前言目…

【C++进阶】C++11(上)

【C进阶】C11(上) &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;C&#x1f96d; &#x1f33c;文章目录&#x1f33c; 1. C11的发展史 2. 列表初始化 2.1 C98的传统{} 2.2 C11中的{} 2.3 C11中的std::initializer_list 3. 右值引用…

Kaggle竞赛——灾难推文分类(Disaster Tweets)

目录 1. 准备工作2. 资源导入3. 数据处理4. 绘制词云图5. 数据可视化5.1 词数和字符数可视化5.2 元特征可视化5.3 类别可视化 6. 词元分析6.1 一元语法统计6.2 多元语法统计 7. 命名实体识别8. 推文主题提取9. 构建模型9.1 数据划分与封装9.2 模型训练与验证 10. 模型评估11. 测…

jvm虚拟机介绍

Java虚拟机&#xff08;JVM&#xff09;是Java语言的运行环境&#xff0c;它基于栈式架构&#xff0c;通过加载、验证、准备、解析、初始化等类加载过程&#xff0c;将Java类文件转换成平台无关的字节码&#xff0c;并在运行时动态地将其翻译成特定平台的机器码执行。 JVM的核心…

App测试环境部署

一.JDK安装 参考以下AndroidDevTools - Android开发工具 Android SDK下载 Android Studio下载 Gradle下载 SDK Tools下载 二.SDK安装 安装地址&#xff1a;https://www.androiddevtools.cn/ 解压 环境变量配置 变量名&#xff1a;ANDROID_SDK_HOME 参考步骤&#xff1a; A…

K8s中TSL证书如何续期

TSL是什么 K8s中的作用是什么&#xff1f; 在 Kubernetes&#xff08;K8s&#xff09;中&#xff0c;TSL 指的是 Transport Layer Security&#xff0c;也就是传输层安全协议。它是用来保护在网络上传输的数据的安全性和隐私性。 TSL 在 Kubernetes 中的作用包括&#xff1a;…

铜业机器人剥片 - SNK施努卡

SNK施努卡有色行业电解车间铜业机器人剥片 铜业机器人剥片技术是针对传统人工剥片效率低下、工作环境恶劣及生产质量不稳定的痛点而发展起来的自动化解决方案。 面临人工剥片的诸多挑战&#xff0c;包括低效率、工作环境差、人员流动大以及产品质量控制不精确等问题。 人工剥片…