当前位置: IT培训 > HTML5培训 > 前端开发 > JS > 一步一步教你 JavaScript 函数式编程(一)
一步一步教你 JavaScript 函数式编程(一) 时间:2017-08-24     来源:JS开发小赢家

在 JavaScript中,我们可以使用 Array.prototype.reduce对列表中的元素进行分组,我们在之前的文章中介绍过。当然如果你不熟悉的话,现在可以去看看;但基本思想是,reduce允许我们通过对数组中的每个元素进行某些迭代操作来构建一个新的值。如图:

 JavaScript 函数式编程

通常,您会考虑使用一些值列表,并使用 reduce来生成一个单独的新值,例如:

通常,您会考虑使用一些值列表,并使用 reduce来生成一个单独的新值,例如:

[1234].reduce(function(sum n) { return sum += n; }, 0); // 10

正是这种迭代数组元素的能力,并构建一个新的值,允许我们使用 reduce来执行分组操作。例如:

 JavaScript 函数式编程

在上面的代码段中,我们使用 reduce来迭代一个对象数组list。我们使用一个空对象作为起点,并根据年龄对记录进行分组。这样我们可以像 map一样处理一个对象,将记录分配给结果对象上由属性名称标识的分组。

让我们通过 reduce使用这个功能来创建一个 group函数。

 JavaScript 函数式编程

如图:

 JavaScript 函数式编程

group和 groupBy按列表中的每个对象中的 prop属性名分组。如果 prop是一个函数,它将使用通过 prop传递每个值的结果。这中工作方式类似于 Lodash和 Underscore.js库中的 _.groupBy 。

这是我们以前的例子,现在使用 groupBy :

在本例中,我们传递一个函数,返回一个字符串作为分组操作的 prop参数。但是我们可以使用非函数形式 prop来操作类似于 JSON响应数据这样的记录,我们要根据记录中的给定属性进行分组:

 JavaScript 函数式编程

这看起来应该很有效。但是,我们上面分组的对象列表只能属于一个可能的组:’letter’或 ‘number’。对象列表与分组键具有多对一的关系。

在我们的 JSON响应数据中,情况并非如此,因为每篇文章记录的tag属性是包含一个或多个标签名称的数组。

多对多关系的分组?

那么,我们是否遇到了麻烦,构建了一个无法满足我们需求的groupBy函数呢?绝对不是!毕竟,这是函数式编程,所以我们只需要使用其他函数合成的函数来构建我们想要的函数就可以!

让我们回顾一下 JSON响应数据;换个角度看,它就像一个典型的数据库表,如图:

 JavaScript 函数式编程

我们需要一种方法来拆分我们的列表,以便我们输出一个列表,在该列表中,每个标签和文章记录都一一对应。该输出列表非常类似于数据库中使用的链接或连接表,具有多对多关系的表 –在本例中为标签到文章(tags to posts)。

这样做必然会在我们的输出中创建副本;但是我们需要这些,因为根据我们的需求,一个post可以出现在多个分组中。

我们的输出列表将类似于下面给出的表示例,如图:

 JavaScript 函数式编程

在上面的图表中,很明显,我们所做的是将输出一个组合。在本例中,我们输出的是文章记录与每个标签的组合。

我们知道我们需要映射我们的列表,所以让我们创建一个 map和 mapWith函数,我们可以使用它,如图:

 JavaScript 函数式编程

现在,我们来创建一个 pair函数,来组合两个列表中的元素。

 JavaScript 函数式编程

我们基本上使用两个列表,在第一个列表中对每一项进行映射,对于每个项目,输出将该项与第二个列表中的每一项相结合的结果,并使用一个嵌套的映射。我们还允许一个函数返回一个列表作为第二个参数,它将从每个迭代的第一个列表中传递该项。

让我们用之前的过滤记录来试试这个。我们将使用柯里化的 getWith传递一个函数作为第二个参数,它将返回每条文章记录上的tags数组,作为第二个集合来进行组合。

 JavaScript 函数式编程

有趣的是,那些是正确的 tag->post对,但是它们嵌套在二维数组中,因为我们已经嵌套了 mapWith调用,每个都返回一个数组。

然而,我们可以使用另一个叫 flatten工具函数来使它变成一维数组,例如,将[[1,2],[3,4]]转换为[1,2,3,4]。

 JavaScript 函数式编程

我们在此处使用 reduce来构建新数组,将数组中的值直接连接到结果数组中,删除嵌套。给我们以下内容:

 JavaScript 函数式编程

现在,我们现有的数据结构让我们想开始进行分组!

但是,非常普遍的操作是,在我们映射它们时 flatten嵌套列表 –它通常被合成一个单独的函数flatMap(list, fn)。

让我们创建一个 flatMap函数和柯里化的 flatMapWith。如图:

我们可以在我们的 pair函数中使用它,以确保正确的输出。如图:

 JavaScript 函数式编程

 JavaScript 函数式编程

现在我们可以将整个分组流程放在一起,其中包括:

使用 pairWith创建一个 tag -> post多对多的列表

使用新列表作为 groupBy的输入,以通过其给定的 tag(每对中的第二项)对每个记录进行分组。

 JavaScript 函数式编程

清理

因此,我们将列表重新构建为了多对多的列表,然后按标签分组,我们终得到的结构仍然不是我们想要的 ——每个文章记录仍然嵌套在一个数组中,其中包含了它的分组关键字键。

我们需要一种方法来映射我们的输出对象的属性,然后映射到每个数组,并使用文章记录来替换每个数组。

我们可以使用 map及其变体映射已经存在的数组;但是如果我们将对象视为一个列表,其中每个项目是属性及其值,我们也可以对对象执行相同的操作。

我们称之为 mapObject,它也会返回一个对象。

 JavaScript 函数式编程

传递给 mapObject的函数不仅传递项,还传递属性名。现在,我们可以使用它来映射一个对象来转换成我们想要的结构了:

 JavaScript 函数式编程

更具声明性

以上使用的操作,我们想从对象列表中提取一个特定属性值,mapWith(getWith(prop)),这是一个相当常见的操作。因此,这通常被命名为pluck,你可以在许多函数库中找到它。

 JavaScript 函数式编程

这是更声明性的,并提供了我们可以重用的另一个高阶函数。但是,我们希望我们的代码能够更详细地描述它实际执行的操作 ——从每个嵌套对中获取文章记录。

我们先来看看我们传递给 mapObjectWith的函数:

啊,这样更具描述性。并结合我们原始的解决方案,我们实际执行的操作变得更具声明性。

var finalgroups = mapObjectWith(getPostRecords)(groupedtags);

完整的实现

满足第二项需求的终实现:

 JavaScript 函数式编程

在这篇文章中,我们为我们的库添加了一些实用函数。我们还采用了一种迂回的方法,将初始数据转换为文章和标签之间的多对多关系。然后,我们可以为每个标签输出一个贴子列表。

我们还研究了一些常见的函数式编程和合成风格, pluck , map和 mapObject。请浏览这个 gist ,以确保理解我们该系列文章第二部分完整的源代码。

X