【玩转 JS 函数式编程_008】3.1.2 JavaScript 函数式编程筑基之:箭头函数——一种更流行的写法

news2024/11/25 22:25:39

文章目录

  • 3.1.2 箭头函数——更流行的方式 Arrow functions - the modern way
    • 1. 返回值 Returning values
    • 2. this 值的处理 Handling the this value
    • 3. arguments 的处理 Working with arguments
    • 4. 单参数还是多参数? One argument or many?

写在前面
故天将降大任于是人也,必先苦其心志,劳其筋骨,饿其体肤,空乏其身,行拂乱其所为,所以动心忍性,曾益其所不能。
——《孟子·告子章句下·第十五节》

3.1.2 箭头函数——更流行的方式 Arrow functions - the modern way

尽管箭头函数工作原理与其他函数几乎殊无二致,但与普通函数相比还是存在一些关键差异(详见 箭头函数 MDN 文档)。箭头函数可以不带 return 语句隐式地返回某个值、同时也没有绑定 this(即函数的上下文)的操作、且不存在 arguments 对象;它们不能被用作构造函数(constructors),也没有 prototype 属性(property),并且由于不允许使用 yield 关键字,也无法用作生成器函数(generators)。

本节我们将讨论以下几个与 JavaScript 函数相关的话题:

  1. 如何返回不同的函数值;
  2. 如何处理 this 值带来的问题;
  3. 参数个数不固定时的处理;
  4. 一个重要概念:科里化currying)(后续章节将多次用到)。

1. 返回值 Returning values

根据 Lambda 演算的编码风格,函数仅由一个结果构成。简化起见,新增的箭头函数也提供了相应的语法支持。当写作 (x, y, z) => 并后跟一个表达式时,就隐式包含了一个 return 语句。例如下面的两个函数就与前面演示的 sum3() 函数功能相同:

const f1 = (x: number, y: number, z: number): number =>
  x + y + z;
const f2 = (x: number, y: number, z: number): number => {
  return x + y + z;
};

如若返回的是一个对象,则必须添加小括号,否则 JavaScript 会误以为后面跟的是代码。为了避免您认为这是个小概率事件,请参阅本章最后 思考题 中的 问题3.1。这是一个非常常见的情况!

关于代码风格的说明

在定义一个单参数函数时,参数两边的小括号可以忽略。为保持风格一致,笔者更倾向于始终保留小括号。然而,本书使用的格式化工具 Prettier(第一章《入门函数式编程的若干问题》提到过)最初倾向于默认不保留;但在 2.0 版本中,配置项 arrow-parens 的默认值已由先前的 avoid(尽量不使用小括号)改为了 always(始终保留小括号)。

2. this 值的处理 Handling the this value

JavaScript 的一个经典问题是 this 值的处理,该取值往往并不按您的“套路”出牌。最终 ES2015 通过箭头函数成功解决了 this 的指向问题。来看下面这个例子:当超时函数被调用时,this 会指向全局变量(window)而非新的对象,因此控制台输出的是 undefined

function ShowItself1(identity: string) {
  this.identity = identity;
  setTimeout(function () {
    console.log(this.identity);
  }, 1000);
}
var x = new ShowItself1("Functional");
// 一秒后显示 undefined,而非 Functional

解决这个问题,传统 JavaScript 有两个经典方案,此外还有一个新增的箭头函数的方案:

  • 传统方案一:利用闭包的特性,定义一个局部变量(通常命名为 thatself),这样该变量就能获取到 this 的原始值,而非 undefined
  • 传统方案二:使用 bind() 函数,将超时函数的 this 绑定到正确的值上(上一节介绍《λ表达式与函数》时也有类似应用);
  • 箭头函数版:这是更新潮的写法,无需其他改动就能获取到正确的 this 值(直接指向对象)。

三种方案的代码实现如下:第一个 timeout 函数使用了闭包,第二个用到了函数绑定,第三个则用到了箭头函数:

// 接上段代码...

function ShowItself2(identity: string) {
  this.identity = identity;
  const that = this;
  setTimeout(function () {
    console.log(that.identity);
  }, 1000);

  setTimeout(
    function () {
      console.log(this.identity);
    }.bind(this),
    2000
  );

  setTimeout(() => {
    console.log(this.identity);
  }, 3000);
}

const x2 = new ShowItself2("JavaScript");
// 一秒后显示 "JavaScript"
// 再过一秒同样显示 "JavaScript"
// 又过一秒还是显示 "JavaScript"

运行上述代码,控制台将在一秒后出现 JavaScript;然后又过了一秒,再次看到 JavaScript;再过 1 秒,控制台会第三次看到 JavaScript

图 1 三种解决方案在控制台中的实际执行情况

【图 1 三种解决方案在控制台中的实际执行情况】

三种方法都能正确运行,具体选哪一个视个人喜好决定。

3. arguments 的处理 Working with arguments

前两章介绍过展开运算符(...)的一些用法。然而,它最常见的使用场景却是与 arguments 对象的处理密切相关(后续第六章会详述)。先来重温上一章的 once() 函数:

// once.ts
const once = <FNType extends (...args: any[]) => any>(
  fn: FNType
) => {
  let done = false;
  return ((...args: Parameters<FNType>) => {
    if (!done) {
      done = true;
      return fn(...args);
    }
  }) as FNType;
};

为什么在写了 return (...args) => 这句后,第 9 行又来一个 func(...args) 呢?这与当下主流观点在处理 函数参数个数不固定 时的具体方式有关,包括没有参数的情况在内。那么,老版本的 JavaScript 是怎么处理这个问题的呢?答案是 arguments 对象(注意它 不是 数组,详见 MDN 官方文档),通过它来访问到实际传入的参数。

arguments 恰巧是一个 类数组对象(array-like object),并不是真正的数组——它唯一拥有的数组属性,便是 length;除此之外,arguments 无法调用 map()forEach() 等任何数组方法。要将 arguments 转换为真正的数组,则必须使用 slice() 方法,并通过 apply() 方法来调用另一个函数,如下所示:

function somethingElse() {
  // get arguments and do something
}

function useArguments() {
  ...
  var myArray = Array.prototype.slice.call(arguments);
  somethingElse.apply(null, myArray);
  ...
}

而使用新版 JavaScript 语法,则无需考虑 argumentsslice 以及 apply

function useArguments2(...args) {
  ...
  somethingElse(...args);
  ...
}

查看上述代码您需要牢记以下三点:

  • 写下 listArguments2(...args) 表明新函数将接收若干个参数(也可能不带参数);
  • 无需任何手动处理就能获得一个参数数组;args 是一个真正的数组;
  • 写成 somethingElse(...args) 比写成 apply() 更加清晰明了。

顺便提一下,当前版本的 JavaScript 依旧支持 arguments 的使用,若要用它来创建数组,有两种替代方案可以实现,不必使用 Array.prototype.slice.call

  • 使用 from() 方法,具体写作:myArray = Array.from(arguments)
  • 直接写作:myArray = [... arguments],这也是扩展运算符的另一种用法。

在后续介绍高阶函数、需要用函数来处理其它函数时,如果遇到参数数量不固定的情况,上述写法会变得非常普遍。

JavaScript 为这类问题提供了简洁高效的写法,因此必须尽快熟悉。这笔投资相当划算!

4. 单参数还是多参数? One argument or many?

编写一个返回值为函数的函数也是可行的,后续第六章还会见到更多这样的情况。例如,按照 λ 算子的演算要求,当中用到的函数没有参数为多个的情况,只接受一个参数;这时就可以通过一项称为 柯里化(currying) 的处理技术来解决这个问题(这么做是何用意?这里先卖个关子,暂且按下不表)。

拓展:双重嘉奖

科里化(Currying)得名于这一概念的提出者 Haskell Curry。值得一提的是,另一门函数式编程语言也被冠名为 Haskell —— 这也算是对其杰出贡献的双重认可(double recognition),可谓梅开二度!

举个例子,之前演示过的三数求和的函数就可以写成下列形式:

// sum3.ts
const altSum3 = (x: number) => (y: number) => (z: number)
  =>
    x + y + z;

这里的函数为什么重命名了呢?简单来说,它已经与之前定义的函数 sum3() 不一样了:sum3() 的类型为 (x: number, y: number, z: number) => number;而 altSum3() 的类型则是 (x: number) => (y: number) => (z: number) => number,二者截然不同(了解更多信息,可参阅本章最后的思考题 3.3)。尽管如此,后者也能得到与之前完全相同的结果。来看看它的具体用法。例如,要对数字 1、2、3 求和:

altSum3(1)(2)(3); // 6

思考

继续往下读之前,不妨思考一下:如果执行的是 altSum3(1, 2, 3),会得到什么样的结果?提示:结果并非是数字!完整答案参见下文。

该函数是怎么运行的呢?不妨将其拆分为多次调用,这也是上面那句表达式在 JavaScript 解释器上的实际计算方式:

const fn1 = altSum3(1);
const fn2 = fn1(2);
const fn3 = fn2(3);

开动您的函数式思维大脑!根据定义,调用 altSum3(1) 的结果,应该是一个 函数。该函数利用了闭包,可以等效解析为如下形式:

const fn1 = y => z => 1 + y + z;

此时的 altSum3() 函数只单独接受一个参数,而非三个参数;其运行结果,fn1,也是一个只接受单个参数的新函数。再运行 fn1(2) ,结果同样是一个函数,同样也只接受一个参数,它等效于:

const fn2 = z => 1 + 2 + z;

再运行 fn2(3),才得到最终结果。如前所述,该函数执行的运算与之前看到的版本是一样的,但实现方式上却有着天壤之别。

您可能会觉得柯里化只是一种取巧的操作罢了:谁会只调用单参数的函数呢?在本书后续第八章《函数的连接》和第十二章《构建更好的容器》讲到如何将函数连接在一起时,您就能明白这么做的根本原因了,届时将多个参数从上一步传递到下一步的操作是无效的。

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

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

相关文章

儿童需要学习C++多久才能参加信息学奥赛的CSP-J比赛?

信息学奥赛&#xff08;NOI&#xff09;是国内编程竞赛领域的顶尖赛事&#xff0c;而对于初学者来说&#xff0c;参加NOI的第一步通常是通过CSP-J&#xff08;全国青少年信息学奥林匹克联赛初赛&#xff09;&#xff0c;这也是面向青少年程序员的入门级竞赛。作为信息学奥赛的基…

vue3使用three.js加载.obj模型示例

vue3使用three.js加载.obj模型示例 效果&#xff1a; 代码&#xff1a; 需要先安装three.js npm install three<template><div ref"threeContainer" class"three-container"></div> </template><script> import * as TH…

男单新老对决:林诗栋VS马龙,巅峰之战

听闻了那场激动人心的新老对决&#xff0c;不禁让人热血沸腾。在这场乒乓球的巅峰之战中&#xff0c;林诗栋与马龙的对决无疑是一场视觉与技术的盛宴。 3:3的决胜局&#xff0c;两位选手的每一次挥拍都充满了策略与智慧&#xff0c;他们的每一次得分都让人心跳加速。 林诗栋&am…

10.6学习

1.Hystrix / Sentinel ●服务雪崩场景 自己即是服务消费者&#xff0c;同时也是服务提供者&#xff0c;同步调用等待结果导致资源耗尽 ●解决方案 服务方&#xff1a;扩容、限流&#xff0c;排查代码问题&#xff0c;增加硬件监控 消费方&#xff1a;使用Hystrix资源隔离&a…

JavaSE——面向对象10:抽象类、接口

目录 一、抽象类 (一)抽象类的引出 (二)抽象类基本介绍 (三)注意事项和使用细节 (四)抽象类的最佳实践——模板设计模式 二、接口 (一)接口快速入门 (二)基本介绍 (三)注意事项与使用细节 (四)接口VS继承 (五)接口的多态性 1.多态参数 2.多态数组 3.接口存在多态…

CoreGen项目实战——代码提交信息生成

数据与相关代码见文末 1.概述 源代码与自然语言之间的语义鸿沟是生成高质量代码提交信息的一个重大挑战。代码提交信息对于开发者来说非常重要,因为它们简明扼要地描述了代码更改的高层次意图,帮助开发人员无需深入了解具体实现即可掌握软件的演变过程。手动编写高质量的提交…

Vite多环境配置与打包:

环境变量必须以VITE开头 1.VITE_BASE_API&#xff1a; 在开发环境中设置为 /dev-api&#xff0c;这是一个本地 mock 地址&#xff0c;通常用于模拟后端接口。 2.VITE_ENABLE_ERUDA&#xff1a; 设置为 "true"&#xff0c;表示启用调试工具&#xff0c;通常是为了…

Elasticsearch学习笔记(六)使用集群令牌将新加点加入集群

随着业务的增长&#xff0c;陆续会有新的节点需要加入集群。当我们在集群中的某个节点上使用命令生成令牌时会出现报错信息。 # 生成令牌 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s node出现报错信息&#xff1a; Unable to create enrollment…

VMware WorkStation Pro 15.5(低版本安装) 教学用

VMware WorkStation Pro 15.5(低版本安装) 教学用 文章目录 VMware WorkStation Pro 15.5(低版本安装) 教学用前言安装使用 前言 VMware Workstation Pro 15.5 是一款功能强大的桌面虚拟化软件&#xff0c;适用于在单台物理电脑上运行多个操作系统。它被广泛应用于软件开发、测…

【文献阅读】Attention Bottlenecks for Multimodal Fusion

Abstract 在多模态视频分类中&#xff0c;将各模态的最终表示或预测进行后期融合&#xff08;“后期融合”&#xff09;仍然是主流范式。为此&#xff0c;本文提出了一种基于 Transformer 的新型架构&#xff0c;该架构使用“融合瓶颈”在多个层次进行模态融合。与传统的成对自…

科研必备语料库

1. Corpus of Contemporary American English 链接&#xff1a;https://www.english-corpora.org/coca/ 2. Purdue Online Writing Lab 链接&#xff1a;https://owl.purdue.edu/owl/ 3. Academic Phrases and Vocabulary 链接&#xff1a;https://www.ref-n-write.com/blog…

IntelliJ IDE 插件开发 | (十三)自定义项目脚手架(下)

系列文章 本系列文章已收录到专栏&#xff0c;交流群号&#xff1a;689220994&#xff0c;也可点击链接加入。 前言 在上一篇文章中介绍了如何在 IDEA 中自定义项目脚手架&#xff0c;本文将介绍如何在WebStorm、PyCharm、CLion等其它 IntelliJ 主流平台中如何自定义项目脚手…

【论文速看】DL最新进展20241006-视频深度估计、3D、自监督学习

目录 【视频深度估计】【3D】【自监督学习】 【视频深度估计】 [TPAMI 2024] NVDS: Towards Efficient and Versatile Neural Stabilizer for Video Depth Estimation 论文链接&#xff1a;https://arxiv.org/pdf/2307.08695 代码链接&#xff1a;https://github.com/RaymondW…

地理空间数据存储与处理:MySQL空间数据类型的优化与应用!

在 MySQL 数据库中&#xff0c;空间数据类型用于存储和处理地理空间数据。这些数据类型允许我们在开发时可在数据库中存储和操作地理位置、几何形状和地理空间关系等信息。 一、什么是空间数据类型 MySQL 中的空间数据类型主要包括以下几种&#xff1a; GEOMETRY&#xff1a…

【无人水面艇路径跟随控制3】(C++)USV代码阅读: ROS包的构建和管理:包的依赖关系、包含目录、库文件以及链接库

【无人水面艇路径跟随控制3】&#xff08;C&#xff09;USV代码阅读&#xff1a; ROS包的构建和管理&#xff1a;包的依赖关系、包含目录、库文件以及链接库 写在最前面ROS是什么CMakeLists.txt总结详细解释CMake最低版本和项目名称编译选项查找catkin包catkin包配置包含目录添…

(刷题记录5)盛最多水的容器

盛最多水的容器 题目信息&#xff1a;题目思路(环境来自力扣OJ的C)&#xff1a;暴力枚举&#xff1a;双指针&#xff1a;移动高度较高的指针移动高度较低的指针 复杂度&#xff1a;代码与注释&#xff1a;暴力枚举&#xff1a;双指针&#xff1a; 题目信息&#xff1a; 给定一…

windows 找不到文件 Microsoft Net Framework 3.5 windows Driver Foundation(WDF).exe

问题 正常更新windows 11的时候发现这个问题。 重启也无法完成下面的更新&#xff0c;重启之后还是显然要更新&#xff1a; 解决方法 中文网站没有找到解决方案。微软官网总是给不靠谱的解决方案。 从有关上看到一个印度语音的视频&#xff0c;用的方法可行。借鉴过来。 …

【机器学习】机器学习框架

机器学习框架是支持开发、训练、和部署机器学习模型的工具集和库&#xff0c;以下是一些主流的机器学习框架及其特点&#xff1a; 1. TensorFlow 特点: 由 Google 开发&#xff0c;支持从研究到生产的大规模部署&#xff0c;广泛应用于深度学习模型。优势: 强大的可扩展性&am…

golang gin入门

gin是个小而精的web开发框架 官方文档 安装 go get -u github.com/gin-gonic/gin最简单的起手代码 package mainimport ("net/http""github.com/gin-gonic/gin" )func main() {r : gin.Default()r.GET("/ping", func(c *gin.Context) {c.JSON…

【自用】王道文件管理强化笔记

文章目录 操作系统引导:磁盘初始化文件打开过程角度1文件的打开过程角度2 内存映射的文件访问 操作系统引导: ①CPU从一个特定主存地址开始&#xff0c;取指令&#xff0c;执行ROM中的引导程序(先进行硬件自检&#xff0c;再开机) ②)将磁盘的第一块–主引导记录读入内存&…