【玩转 JS 函数式编程_010】3.2 JS 函数式编程筑基之:以函数式编程的方式活用函数(上)

写在前面
按照惯例,过长的篇幅分开介绍,本篇为 JavaScript 函数式编程核心基础的第二部分——以函数式编程的方式活用函数的上篇,分别介绍了 JS 函数在排序、回调、Promise 期约、以及连续传递等应用场景下的用法演示。和之前章节相比难度又有一定的提升。准备好了吗?

3.2. 以函数式编程的方式使用函数 Using functions in FP ways

有几种常见的编码模式实际上采用了函数式编程的风格,而您甚至都未曾察觉。本节将逐一考察这些模式的具体表现形式,以便您更快地习惯这种编码风格。这些模式包括:

  • 注入模式:用于筛选不同策略及其他用途;
  • 回调与期约模式:将引入连续传值的相关概念;
  • 填充与插入模式;
  • 立即调用策略模式。

3.2.1. 注入——整理出来 Injection – sorting it out

Array.prototype.sort() 方法提供了第一个将函数作为参数传递的示例。 给定一个待排序的字符串数组,则可以调用 array.sort() 方法。例如,将彩虹的颜色按字母顺序排序,代码如下:

var colors = ["violet","indigo","blue","green","yellow","orange","red"
];
colors.sort();
console.log(colors);
// ["blue", "green", "indigo", "orange", "red", "violet", "yellow"]

注意,这里的 sort() 方法并不需要任何参数,数组也能完成排序。默认情况下,此方法按字符串的 ASCII 编码进行排序。因此,如果用它对数字型数组排序则会出错,按这种方式得出的结果,数字 20 将位于 1003 之间,因为 10020 之前(排序元素均被视作字符串)而 20 又在 3 之前:

var someNumbers = [3, 20, 100];
someNumbers.sort();console.log(someNumbers);
// [100, 20, 3]

假如不考虑数字,只对字符串按默认规则排序。此时如果要对一组西班牙语单词(palabras)进行排序,在遵循恰当的本地化语言环境规则时又会如何呢?可以看到,结果并不正确:

var palabras = ["ñandú", "oasis", "mano", "natural", "mítico", "musical"];
palabras.sort();console.log(palabras);
// ["mano", "musical", "mítico", "natural", "oasis", "ñandú"] -- wrong result!

拓展知识

对于语言或生物学爱好者而言,ñandú 的英文是 rhea,它一种类似于鸵鸟的飞禽。虽然以 ñ 开头的西班牙语单词并不多,而笔者的国家乌拉圭恰好就有这些鸟类——这就是存在特殊单词的客观原因。

哎呀!在西班牙语中,ñ 介于 no 之间,但 ñandú 排到了末尾。此外,mítico(对应英文 mythical,注意重音字母 í)本应出现在 manomusical 之间,波浪号应该被忽略。要解决这个问题,需要向 sort() 传入正确的比较函数。本例可以使用 localeCompare() 方法:

palabras.sort((a, b) => a.localeCompare(b, "es"));console.log(palabras);
// ["mano", "mítico", "musical", "natural", "ñandú", "oasis"]

这里的语句 a.localeCompare(b,"es") 会对 ab 进行比较:根据西班牙语("es")的排序规则,当 a 先于 b 时返回一个负值;a 落后于 b 时返回一个正值;两者相等时返回 0

现在排序结果正确了!此时可引入一个新函数 spanishComparison() 来替换所需的字符串比较规则,可使代码更加清晰:

const spanishComparison = (a, b) => a.localeCompare(b, "es");palabras.sort(spanishComparison);
// sorts the palabras array according to Spanish rules:
// ["mano", "mítico", "musical", "natural", "ñandú", "oasis"]

在接下来的章节中,我们将讨论函数式编程如何让您以更贴近声明式的方式来编写代码,生成更易于理解的代码。这类微小的改变是很有帮助的:当阅读代码的人读到排序这部分时,他们就可以在不借助注释的情况下立即推断出将会执行的逻辑。

小贴士

这种通过注入不同的比较函数来改变 sort() 函数工作方式的模式,实际上是策略设计模式的一种表现形式。第 11 章《实现函数式的设计模式》会具体论述。

提供一个排序函数作为参数(典型的函数式编程风格)还有助于解决其他问题,例如:

  • sort() 仅适用于字符串。要对数字进行排序,必须提供一个数字排序函数,如:myNumbers.sort((a,b) => a-b)
  • 如要按给定属性对对象排序,则需要传入一个与该属性值进行比较的函数。如:myPeople.sort((a,b) => a.age - b.age) 可以按年龄升序对人员进行排序。

小贴士

更多 localeCompare() 介绍,请参阅 MDN 官方文档。您可以指定区域规则、大小写字母的排序规则以及是否忽略标点符号等。但请注意:并非所有浏览器都支持所需的额外参数。

这是一个您以前可能用过的简单示例,但它毕竟是一种函数式编程模式。接下来让我们来看看调用 Ajax 时将函数作为参数的更常见用法。

3.2.2. 回调、期约及延续 Callbacks, promises, and continuations

作一等对象传参的函数最常用的示例应该就是回调(callbacks)和期约(promises)了。在 Node 环境下,读取文件是异步完成的:

const fs = require("fs");fs.readFile("someFile.txt", (err, data) => {if (err) {console.error(err); // or throw an error, or otherwise handle the problem} else {console.log(data.toString()); // do something with the data}
});

readFile() 需要一个回调函数——本例为一个匿名函数——它将在文件读取操作完成时被调用。

更好的方法是使用 Promise,详细介绍参考 MDN 文档。有了 Promise,当使用更现代的 fetch() 函数执行 Ajax 调用 Web 服务时,可以按以下代码执行一些逻辑:

fetch("some/remote/url").then(data => {// Do some work with the returned data}).catch(error => {// Process all errors here});

提示

请注意,如果定义了适当的 processData(data)processError(error) 函数,则代码可以像之前提过的那样,精简为 fetch("some/remote/url").then(processData).catch(processError)

最后,还应该考虑使用 async / await,具体用法详见 MDN 文档 async_function 和 await operator。

3.2.3. 连续传递风格 Continuation passing style

前面的代码,在调用一个函数的同时,还传递了另一个在 I/O 操作完成时要执行的函数,可以认为是 连续传递风格CPS,Continuation Passing Style)的一种具体体现。这是一种什么样的编码技术呢?不妨从一个实际问题切入理解:如果禁止使用 return 语句,该怎样编程?

乍一看,这个问题似乎无从下手。然而,通过 将回调传函数递给被调用函数,我们便能寻得解决之道:当该过程准备返回控制权给调用者时,它不会实际返回,而是去调用所传递的回调函数。这么一来,回调函数就为被调用函数提供了延续该操作过程的一种途径,CPS 风格中的“连续”(continuation)就是这么来的。CPS 风格本节不具体展开,留待第九章《函数设计——递归》再进行深入研究。值得一提的是,正如我们将看到的那样,CPS 风格将有助于规避递归中的一个重要限制。

弄清“连续”的具体用法,有时是一件颇具挑战的事,但总归是能够达成的。这种编码方式一个有趣的好处在于,通过指定程序的接续方式,可以打破所有常见的程序控制结构(ifwhilereturn 等等),实现想要的任何控制流程。对于处理过程未必是线性的某些问题而言,这类编码风格将会非常有用。当然,这也可能导致您新发明的某种控制结构,远比想象中使用 GOTO 语句的后果更为糟糕!这种做法的危险如下图所示:

图 3.1 弄乱程序流程,可能会发生什么更糟糕的情况呢?

【图 3.1 弄乱程序流程,可能会发生什么更糟糕的情况呢?】

拓展

这部 XKCD 漫画可以在 这里 在线访问。

此外,可供传递的“连续”体也可以不止一个。就像 Promise 那样,可以提供两个或多个回调逻辑参与传递。顺便说一句,这一特性可用于异常处理领域:如果只是允许一个函数可以抛出一个错误,那么该错误就很可能潜在地返回给调用者,而事实上我们并不希望这样。解决问题的关键在于:提供另一个专门处理报错的回调函数(即不同的连续体),以便在抛出异常时使用(第十二章《构建更好的容器——函数式数据类型》将提出一个基于 monads 的新解决方案):

function doSomething(a, b, c, normalContinuation, errorContinuation) {let r = 0;// ... do some calculations involving a, b, and c,// and store the result in r// if an error happens, invoke:// errorContinuation("description of the error")// otherwise, invoke:// normalContinuation(r)
}

利用 CPS 甚至可以超越 JavaScript 现有的控制结构,但这超出了本书的讨论范围,感兴趣的读者可自行研究。

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

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

相关文章

详细分析Spring Framework中 @ConditionalOnProperty的基本知识(附Demo)

目录 前言1. 基本知识2. Demo 前言 基本的Java知识推荐阅读: java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)【Java项目】实战CRUD的功能整理(持续更新) 从实战中学习启发 1. 基本知识 Conditiona…

C++:thread | condition_variable|mutex

欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 文章目录 引言thread创建线程传递参数给线程函数 mutexmutex常见用法 condition_variable:条件变量生产消费模型 引言 相信大家在Linux系统编程中都接触过线程创建和退出的相关系统调用&#xff0…

【学习笔记】一种使用多项式快速计算 sin 和 cos 近似值的方法

一种使用多项式快速计算 sin 和 cos 近似值的方法 在嵌入式开发、游戏开发或其他需要快速数学计算的领域,sin 和 cos 函数的计算时间可能会影响程序的整体性能。特别是在对时间敏感、精度要求不高的场景中,传统的 sin 和 cos 函数由于依赖复杂的数值方法…

RHCSA的学习(4)

一、vi编辑器 (1)为什么学vi? 所有的Unix Like 系统都会内建 vi 文本编辑器,其他的文本编辑器则不一定会存在; 很多个别软件的编辑接口都会主动呼叫 vi (例如未来会谈到的 crontab, visudo, edquota 等指令)&#x…

【笔记】Day2.4表设计说明

主键ID一般使用bigint类型 运送类型 使用比int更小的tinyint类型 eg:普快代表1 特快代表2(没写反) 关联城市 varchar 2代表京津冀 3代表江浙沪 4代表川渝 首重和续重都有小数点 故使用double 轻抛系数都为整数 故使用int 创建时间和修改…

【UI】将 naive ui 的 message 封装进axios 中,关于naiveui的message相关的用法

文章目录 前言在setup外进行使用直接包裹使用vue 单文件中使用 参考文章: 关于naiveui的message相关的用法 前言 最近新建了一个vite vu3 的项目,完全是从0 到1 ,封装到request 的时候 想对axios 请求做一个全局的处理,但发现…

jmeter学习(7)beanshell

beanshell preprocessor 发送请求前执行 beanshell postprocessor 发送请求前执行 获取请求相关信息 String body sampler.getArguments().getArgument(0).getValue(); String url sampler.getPath(); 获取响应报文 String responseprev.getResponseDataAsString(); 获…

实战OpenCV之视频处理

基础入门 视频是由一系列连续的图像帧组成的,这些帧按照一定的速率连续播放,从而形成动态画面。与视频相关的主要参数有:分辨率、帧率、码率、编解码器、帧类型、文件格式等,下面分别进行介绍。 1、帧率。表示每秒显示的图像帧数&…

【devops】x-ui 实现一键安装 x-ray 打造高速国际冲浪 | xray管理平台

一、部署X-UI篇 1、Github 地址&说明 github地址如下: https://github.com/FranzKafkaYu/x-ui?tabreadme-ov-file 2、一键部署 2.1、更新并安装curl #Ubuntu、Deibian系统 apt update && apt upgrade -y apt install curl -y #CentOS7 系统 yum…

强大的JVM监控工具

介绍 在生产环境中,经常会遇到各种各样奇葩的性能问题,所以掌握最基本的JVM命令行监控工具还是很有必要的 名称主要作用jps查看正在运行的Java进程jstack打印线程快照jmap导出堆内存映像文件jstat查看jvm统计信息jinfo实时查看和修改jvm配置参数jhat用…

现代身份和访问管理 IAM 如何降低风险

您的公司是否仍在使用 1998 年时的身份管理系统?仅凭用户名和密码就能登录本地网络并访问几乎所有资源吗? 虽然大多数企业已经转向现代身份和访问管理(IAM) 平台,但成千上万的企业和其他组织仍然依赖过时的用户名/密码系统。 如果你看一下传…

SpringBoot 整合 阿里云 OSS图片上传

一、OOS 简介 ‌阿里云OSS(Object Storage Service)是一种基于云存储的产品,适用于存储和管理各种类型的文件,包括图片、视频、文档等。‌ 阿里云OSS具有高可靠性、高可用性和低成本等优点,因此被广泛应用于各种场景&…

简单的网络爬虫爬取视频

示例代码爬取一个周杰伦相关视频 import requests# 自己想下载的视频链接 video_url https://vdept3.bdstatic.com/mda-qg8cnf4bw5x6bjs5/cae_h264/1720516251158906693/mda-qg8cnf4bw5x6bjs5.mp4?v_from_shkapp-haokan-hbf&auth_key1728497433-0-0-4a32e13f751e04754e4…

oracle-函数-instr()的妙用以及相似功能like

INSTR(C1,C2[,I[,J]]) 【功能】在一个字符串中搜索指定的字符,返回发现指定的字符的位置; 【说明】多字节符(汉字、全角符等),按1个字符计算 【参数】 C1 被搜索的字符串 C2 希望搜索的字符串 I 搜索的开始位置,默认为1 J 第J次出现的位置,默认为1 【…

算法修炼之路之位运算

目录 一:位运算符及一些常用结论总结 1.给一个数n,确定它的二进制表示中的第x位是0还是1(位数从右向左0开始增加) 2.将一个数n的二进制表示形式的第x位修改成1 3.将一个数n的二进制表示的第x位修改为0 4.提取一个数n的二进制表示中最右侧的1 5.干掉一个数n的…

单片机闪存,闪存缓冲取,闪存延迟

一、启用闪存预取缓冲区(FLASH_PrefetchBufferCmd (FLASH_PrefetchBuffer_Enable);) 闪存预取缓冲区的作用: 在微控制器中,闪存是用于存储程序代码和常量数据的非易失性存储器。当微控制器执行程序时,需要从闪存中读取…

kubernetes-强制删除命名空间

一、故障现象 1、删除命名空间卡住、强制删除也卡住 2、其他终端显示命名空间下无资源 二、处理步骤 1、kubectl get namespace cilium-test -o json > temp.json 获取你需要删除的命名空间json描述文件。 2、修改finalize字段 3、替换 kubectl replace --r…

OmniH2O——通用灵巧且可全身远程操作并学习的人形机器人(其前身H2O是HumanPlus的重要参考)

前言 由于我司一直在针对各个工厂、公司、客户特定的业务场景,做解决方案或定制开发,所以针对每一个场景,我们都会反复考虑用什么样的机器人做定制开发 于此,便不可避免的追踪国内外最前沿的机器人技术进展,本来准备…

信息安全工程师(42)VPN类型和实现技术

前言 VPN(Virtual Private Network,虚拟专用网络)是一种在公共网络上建立专用网络连接的技术。 一、VPN类型 VPN可以根据不同的分类标准划分为多种类型,主要包括以下几种: 按协议分类: PPTP(Poi…

创建osd加入集群

故障原因:ceph节点一个磁盘损坏,其中osd69 down了,需要更换磁盘并重新创建osd加入ceph集群。 信息采集: 更换磁盘前,查询osd69对应的盘符: 将对应的故障磁盘更换后,并重做raid,然后查…