现在我们有一个被过滤并按标签分组的对象,我们需要为每个标签组进行一些排序。运行我们的 app.js程序后(注,app.js代码可以在这个 gist中查看),我们输出的对象现在是这样的:
我们需要对每个标签组的记录数组按照发布日期降序(新到旧)顺序进行排序。
列表排序
JavaScript通过 Array.prototype.sort使列表排序相当容易。由于没有比较函数参数,.sort将尝试将元素转换为字符串,并以 Unicode编码顺序进行排序。它还对数组进行排序。我们希望我们的排序是一个 纯函数,不希望改变我们排序的数组;但是返回一个新排序的数组。
纯函数不依赖于且不改变其本身作用域之外的变量状态的函数。如果给定相同的参数,它们将始终返回相同的结果。(愚人码头注:也就是说,纯函数的返回值只由它调用时的参数决定,它的执行不依赖于系统的状态(比如:何时、何处调用它——译者注)。纯函数是函数式编程的一个基础。)
我们创建一个排序函数,它将使用列表和比较函数作为参数,并返回一个新的排序列表。如图:
在这里,我们只是使用 Array.prototype.concat来创建一个新的浅拷贝数组,然后对这个数组进行排序并返回。比较函数通过 Array.prototype.sort一次传递两个对象, a和b,并且应该执行以下操作:
如果 a < b,返回 -1或小于 0(排序将使 a的索引低于 b)
如果 b < a,返回 1或大于0(排序将使b的索引低于a)
如果 a == b,返回 0(排序不会改变 a或 b的索引)
我们来尝试一些列表:
注意:names和 numbers未改变。
创建通用的比较函数
Array.prototype.sort接受的比较函数有一个非常特殊的接口,这在其他地方不是很方便。如果我们可以创建通用的比较函数,那就更好了。二元比较函数返回 true或false ,并将它们作为Array.prototype.sort的比较函数进行重用。
我们需要的是一个高阶函数,它可以接受我们的二元比较函数,返回一个布尔值,并给我们一个新的函数,它可以返回 -1、0或1来满足我们需要的接口。
原来,我们可以很容易地做到这一点。我们称它为 comparator,它采用了一个返回 true或 false的二元比较函数,并返回一个比较函数,该函数保存在 Array.prototype.sort定义的 API中。
现在我们来看看如何使用我们以前的示例数据。
这展示了使用高阶函数的另一个好处:我们可以使用函数,并创建新的函数来完成更复杂或更具体的任务。
高阶函数是指将函数作为参数和/或将函数作为返回值的函数。
比较对象属性
到目前为止,我们的解决方案对于包含数字和字符串等标量值的数组非常有用。但是我们要处理的是对象数组。我们如何使用我们的通用的比较函数以及 comparator根据给定的属性来对对象列表进行排序呢?
幸运的是,我们已经使用过 useWith高阶函数在上一篇文章中解决过这个问题。
useWith允许我们通过传递另一个函数来修改发送给任何函数的参数。在本例中,我们要确保传递给我们的比较函数的两个对象通过我们定义的另一个以前的函数 getWith('published'),以便只比较每个对象上 published属性。
让我们来看看这个实例如何与一些示例代码一起使用:
我自己做了很多特别修改…
按发布日期排序我们的分组
现在,我们有了对输出进行排序的工具。我们需要迭代输出对象 mapObject中的分组,对于每个分组,都是一个列表,按照每个对象的发布日期( sortBy +comparator + useWith ),从新到老的顺序进行排列。
让我们来看一下:
从我们上一篇文章中获取的输出对象,是一个具有属性名与标签匹配的对象,每个对象包含一个未排序的文章记录列表;而这里返回的对象,格式上是相同的,但是已经根据 published属性对记录列表进行了排序。
除了我们在函数库中创建的助手函数之外,在我们的 app.js中,以下是函数调用的主要顺序,用于获取初始输入并创建终输出:
这是一组相当具有描述性的函数集;并且大多数都是纯函数和高阶函数,可用于处理其他输入或数据。实际上这个应用程序只有两个具体的功能,即 getPostRecords和 sortByPublishDate ——所有其他函数都是通过可重用的二元比较函数和构建函数式实用工具库相结合来完成的。
合成和排序函数
在我们的应用程序中你会注意到的一点,那就是我们不断地执行以下操作:
拿一个输入列表或对象
将其传递给一个函数,来创建输出
获取该输出并将其用作输入(转到步骤2)
这是一个序列或 pipeline ,在那里我们有一系列函数,可以输入,处理它,并将其输出传递给系列中的下一个函数。这类似于 UNIX命令行及其 | (pipe)操作符将上一个命令的输出挂接到下一个命令的输入。
$ ps aux | grep "root"
我们可以在这里做同样的事情,这通常被称为函数式编程中的合成。例如,使用一个函数 g并将输出传递给函数 f ,类似于调用 f(g)一样。在本例中,我们在输入端调用函数 g,输出被传递给函数 f作为其参数或输入。
这是一个简单的函数合成,如上所述:
这个简单的 compose函数使用两个函数作为参数,并返回一个函数,当执行时,使用输入调用第二个函数,然后使用该调用的结果再调用第一个函数。
但是,在我们当前的博客文章中,我们有多个函数来处理我们的输入。我们需要一个更灵活的compose,它可以将两个以上的函数作为参数链接在一起。
请注意,compose从右到左将传入的行数链接在一起。这是标准的,调用 compose(f, g)等同于f(g),从右到左,从内到外的顺序。
如图所示:
但是还有一种叫做 pipeline(管道),同样把参数函数连接在一起,但是在左侧输入,在右边获得输出,这样传递函数的顺序就更有意义了。
让我们创建一个 pipeline函数,它与 compose做同样的工作,但是在我们的上下文中,参数顺序颠倒了,是从左到右读取。
如图所示:
var pipeline = flip(compose);
So easy!使用高阶函数非常容易做到这一点。现在,让我们在 app.js中调用之前的调用,并构建一个函数,它将链接每个函数的输入和输出。
如果我们比较一下合成函数的输出,你会看到它与以前的输出相匹配。现在,您还可以看到为什么我们为每个实用函数创建了 right 柯里化版本。
拥有 right 柯里化版本,其中要操作的数据作为第二,右和后一个参数传递,允许我们构建偏应用函数,我们可以将这些函数传递给我们的 compose或 pipeline高阶函数,以创建更复杂的函数。
热点新闻
前端开发技术库