作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Avi雅利安人
验证专家 在工程

Avi是一个精通Python的全栈开发人员, JavaScript, 他也是谷歌代码之夏的多次参与者.

专业知识

以前在

谷歌
分享

函数式编程是一种使用表达式和函数构建计算机程序而不改变状态和数据的范例.

通过尊重这些限制, 函数式编程的目标是编写更容易理解和更防bug的代码. 这可以通过避免使用流控制语句(, , 打破, 继续, 转到),这使得代码更难理解. 也, 函数式编程要求我们编写纯代码, 不太可能出错的确定性函数.

在本文中,我们将讨论如何使用JavaScript进行函数式编程. 我们还将探讨使之成为可能的各种JavaScript方法和特性. 最后, 我们将探索与函数式编程相关的不同概念,并了解它们为何如此强大.

在进入函数式编程之前, 虽然, 需要理解纯函数和非纯函数之间的区别.

纯vs. 不洁净的功能

纯函数接受一些输入并给出一个固定的输出. 此外,它们在外界也不会产生副作用.

常量 添加 = (a, b) => a + b;

在这里, 添加 是一个纯函数. 这是因为,对于一个固定的值 ab,输出将始终是相同的.

常量 秘密 = 42;  
常量 getId = (a) => 秘密 * a;

getId 不是一个纯函数吗. 原因是它使用了全局变量 秘密 用于计算输出. If 秘密 要改变吗 getId 函数对于相同的输入将返回不同的值. 因此,它不是一个纯函数.

让id_count = 0;
常量 getId = () => ++id_count;

这也是一个不纯函数, 这也有几个原因-(1)它使用非局部变量来计算其输出, (2)通过修改外部世界中的一个变量,它在外部世界中产生了副作用.

getId是一个不纯的插图

如果我们必须调试这段代码,这可能会很麻烦.

的现值是多少 id_count? 还有哪些函数在被修改 id_count? 还有其他函数依赖吗 id_count?

由于这些原因,我们在函数式编程中只使用纯函数.

纯函数的另一个好处是它们可以并行化和记忆. 看过前面两个函数了吗. 不可能并行化或记忆它们. 这有助于创建高性能代码.

函数式编程的原则

到目前为止,我们已经了解到函数式编程依赖于一些规则. 它们如下.

  1. 不要改变数据
  2. 使用纯函数:固定输入的固定输出,没有副作用
  3. 使用表达式和声明

当我们满足这些条件时,我们可以说我们的代码是功能性的.

JavaScript函数式编程

JavaScript已经有了一些支持函数式编程的函数. 例子: 字符串.原型.片, 数组.protoype.Filter, 数组.原型.加入.

另一方面, 数组.原型.为Each, 数组.原型.推 是非纯函数.

有人会说 数组.原型.为Each 不是一个不纯的功能设计,但想想看,它是不可能做任何事情,除了改变非本地数据或做副作用. 因此,把它放在非纯函数的类别中是可以的.

此外,JavaScript有一个 常量 声明,这对于函数式编程来说是完美的,因为我们不会改变任何数据.

JavaScript中的纯函数

让我们看一下JavaScript给出的一些纯函数(方法).

Filter

顾名思义,这将过滤数组.

数组.Filter(条件);

这里的条件是一个获取数组中每一项的函数, 它应该决定是否保留项目并为它返回真实的布尔值.

常量 FilterEven = x => x%2 === 0;  
[1, 2, 3].Filter(FilterEven);  
// [2]

请注意, FilterEven 是一个纯函数. 如果它是不纯的,那么它将使整个Filter调用不纯.

Map

map 将数组的每一项映射到一个函数,并根据函数调用的返回值创建一个新数组.

数组.地图(映射)

映射器 一个函数接受数组中的一个元素作为输入并返回输出吗.

常量 double = x => 2 * x;  
[1, 2, 3].地图(双);  
// [2, 4, 6]

Reduce

Reduce 将数组Reduce为单个值.

数组.Reduce(减速器);

减速机 是一个函数,以积累的值和下一项在数组中,并返回新的值. 对数组中的所有值一个接一个地调用它.

常量 sum = (accumulatedSum, 数组Item) => accumulatedSum + 数组Item  
[1, 2, 3].Reduce(总和);
// 6

Reduce呼叫说明

Concat

concat 向现有数组添加新项以创建新数组. 它不同于 推 () 从某种意义上说 推 () 改变数据,使其不纯.

[1, 2].concat ([3, 4])  
// [1, 2, 3, 4]

也可以使用 传播 操作符.

[1, 2, ...[3, 4]]

Object.分配

Object.分配 将值从提供的对象复制到新对象. 因为函数式编程是基于不可变数据的, 我们用它来制作基于现有对象的新对象.

Const obj = {a: 2};  
常量 newObj =对象.分配({},obj);  
newObj.a = 3;  
obj.a;  
// 2

随着 ES6,这也可以使用扩展运算符来完成.

常量 newObj = {...obj};

创建自己的纯函数

我们也可以创建一个纯函数. 我们来做一个复制字符串的例子 n 次数.

常量 duplicate = (str, n) =>  
	n < 1 ? : STR + duplicate(STR, n-1);

这个函数复制一个字符串 n 计时并返回一个新字符串.

复制(“万岁!', 3)  
/ /万岁!万岁!万岁!

高阶函数

高阶函数是接受函数作为参数并返回函数的函数. 通常,它们被用来添加到函数的功能中.

常量 withLog = (fn) => {  
	回报(...args) => {  
		控制台.日志(“$ {fn.名称}’);  
		返回fn (...args);  
	};  
};

在上面的示例中,我们创建一个 withLog 接受一个函数并返回一个函数的高阶函数,该函数在包装函数运行之前记录一条消息.

常量 添加 = (a, b) => a + b;  
常量 添加与Logging = withLog(添加);  
添加与Logging(3、4);  
//调用添加  
// 7

withLog HOF也可以与其他函数一起使用,它的工作没有任何冲突或编写额外的代码. 这就是HOF的美妙之处.

常量 添加与Logging = withLog(添加);  
常量 hype = s => s + '!!!';  
常量 hype与Logging = withLog(炒作);  
hype与Logging(“出售”);  
//调用炒作  
/ /销售!!!

也可以在不定义组合函数的情况下调用它.

withLog(宣传)(“出售”); 
//调用炒作  
/ /销售!!!

局部套用

柯里化意味着将带有多个参数的函数分解为一个或多个高阶函数.

让我们来看看 添加 函数.

常量 添加 = (a, b) => a + b;

当我们要curry它时,我们重写它,将参数分布到多个级别,如下所示.

常量 添加 = a => {
	return b => {
		返回a + b;
	};
};
添加(3)(4);  
// 7

套用的好处是记忆. 现在,我们可以在函数调用中记住某些参数,以便以后可以重用它们,而无需重复和重新计算.

//假设getoffsetnumber()调用是昂贵的
常量 添加Offset = 添加(getOffsetNumber());
添加Offset (4);
// 4 + getOffsetNumber()
添加Offset (6);

这当然比到处使用两个参数要好.

// (x)不要这样做  
添加(4,getOffsetNumber ());  
添加(6,getOffsetNumber ());  
添加(10,getOffsetNumber ());

我们还可以重新格式化我们的curry函数,使其看起来简洁. 这是因为每个级别的curry函数调用都是单行返回语句. 因此,我们可以使用 箭头功能 在ES6中重构它,如下所示.

常量 添加 = a => b => a + b;

作文

在数学方面, 组合定义为将一个函数的输出传递到另一个函数的输入,从而创建组合输出. 在函数式编程中也是一样的,因为我们使用的是纯函数.

为了展示一个示例,让我们创建一些函数.

第一个函数是range,它接受一个起始数字 a 和一个结束的数字 b 并创建一个由数字组成的数组 a to b.

常量 range = (a, b) => a > b ? [] : [a, ...范围(a + b));

然后我们有一个乘法函数,它取一个数组并将其中的所有数字相乘.

常量 multiply = arr => arr.Reduce((p, a) => p * a);

我们将一起使用这些函数来计算阶乘.

常量 factorial = n => multiply(range(1, n));  
阶乘(5);  
// 120  
阶乘(6);  
// 720

上面计算阶乘的函数类似于 F (x) = g(h(x)),从而证明了复合性质.

结束词

我们讲了纯函数和非纯函数, 函数式编程, JavaScript新特性的帮助, 以及函数式编程中的一些关键概念.

我们希望这篇文章能激起您对函数式编程的兴趣,并可能激励您在代码中尝试它. 我们确信这将是您软件开发旅程中的一个学习经验和里程碑.

函数式编程是 好吧-研究健壮的 编写计算机程序的范例. 与 ES6的引入, JavaScript提供了比以往更好的函数式编程体验.

了解基本知识

  • 什么是函数式编程?

    函数式编程是一种使用声明和表达式构建计算机程序的范例.

  • JavaScript是函数式编程语言还是面向对象的编程语言?

    多亏了ES6的新发展, 我们可以说JavaScript既是一种函数式编程语言,也是一种面向对象编程语言,因为它提供了各种一流的特性.

  • 函数式编程的优势是什么?

    函数式编程确保了代码中更简单的流程控制,并避免了变量和状态变化形式的任何意外. 所有这些都可以帮助我们避免bug并轻松理解我们的代码.

  • 还有哪些函数式编程语言?

    Lisp、Erlang、Haskell、Closure和Python是其他函数式编程语言. 在这些, 从某种意义上说,Haskell是一种纯函数式编程语言,它不允许其他编程范式.

  • 什么是ES6?

    ES6或ECMAScript 6是JavaScript的一个新版本,它包含了许多新特性,比如箭头函数, 常量, 传播算子, 在许多其他方面.

聘请Toptal这方面的专家.
现在雇佣
Avi雅利安人

Avi雅利安人

验证专家 在工程

新德里,德里,印度

2018年3月28日成为会员

作者简介

Avi是一个精通Python的全栈开发人员, JavaScript, 他也是谷歌代码之夏的多次参与者.

作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

专业知识

以前在

谷歌

世界级的文章,每周发一次.

<为m aria-label="Sticky subscribe 为m" class="_2ABsLCza P7bQLARO -Ulx1zbi">

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

<为m aria-label="Bottom subscribe 为m" class="_2ABsLCza P7bQLARO -Ulx1zbi">

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® 社区.