分享之前我还是要推荐下我自己的前端学习群:685910553,不管你是小白还是大牛,我都欢迎,不定期分享干货,包括我自己整理的一份2019最新的前端资料和零基础入门教程,送给大家,欢迎初学和进阶中的小伙伴
什么是函数式编程(Functional Programming)**
在开始之前,让我们先花一点时间来了解一下一些实用的函数式编程概念。
函数式编程把“function”作为重复使用的主要表达式。通过构建专注于某个特定任务的小函数,函数式编程使用合成(compose)来构建更复杂的函数 ——这就是 Currying(柯里化)和 Partial Application(偏函数应用)这样的技术发挥作用的地方了。
函数式编程使用函数作为重复使用的声明表达式,避免对状态进行修改,消除了副作用,并使用合成来构建函数。
功能编程本质上是用功能编程的!额外需要考虑的是:如避免状态改变,无副作用的纯函数,消除循环支持递归是纯函数式编程方法的一部分,用 Haskell语言是这样构建的。
我们将重点介绍函数式编程的实用部分,以便我们可以在本系列博客文章中立即使用 Javascript 。
高阶函数(Higher Order Functions) – JavaScript中函数是“一等公民(first-class)”,这意味着我们可以将函数作为参数传递给其他函数;也可以将函数作为其他函数的值返回。渔人码头注:以函数为参数或返回值的函数称为“高阶函数”。
装饰器(Decorators) –因为 JavaScript中函数可以是高阶函数,所以我们可以创建函数来增加其他函数的行为和/或作为其他函数的参数。
合成(Composition) –我们还可以创建由多个函数合成的函数,创建链式的输入处理。
我们将介绍我们要使用的技术,以便在需要时利用这些特性。这让我们可以在上下文环境中引入它们,并使概念易于消化和理解。
让我们开始吧
我们来看一个典型的例子,它需要处理从异步请求中获取的一些数据。在这种情况下,异步获取数据采用了JSON格式,并包含了一个博客文章的摘要列表。
以下是我们将使用的异步获取数据:查看 Gist中的完整数据和示例数据。
我们的需求:现在,假设我们想要显示最近的文章(不超过一个月),按标签分组,按发布日期排序。让我们思考一下我们需要做些什么
过滤掉一个月以前的文章(比如30天)。
通过他们的 tags对文章进行分组(这可能意味着如果他们有多个标签,则会显示在两个分组中。)
按发布日期排序每个标签列表,降序。
我们将在本系列文章中涵盖上述每个需求,这篇文章从过滤开始。
过滤数据
我们的第一步是过滤掉发布日期超过 30天的文章记录。由于函数式编程都是作为重用的主要表达式的函数,所以让我们构建一个函数来封装过滤列表的行为。
有些朋友可能会问,“真的吗?就这样好了吗?”
嗯,是的,没有更多要写的了。
这个函数使用 predicate断言函数(fn)来过滤一个数组(list),或许你会说,这可以通过直接调用 list.filter(fn)来轻松实现。那么为什么不这样做呢?
因为当我们将操作抽象成一个函数时,我们就可以使用 Currying(柯里化)来构建一个更有用的函数。
Currying(柯里化)是使用 N个参数的函数,返回一个 N个函数的嵌套系列,每个函数都采用 1个参数。
有关 Currying(柯里化)概念的更多信息,请阅读我以前的文章,并实现 left -> right的 currying(柯里化) 。
一步一步教你 JavaScript 函数式编程(第一部分)
在这种情况下,我们将使用一个名为 rightCurry的函数,该函数将函数的参数从右向左进行柯里化。通常,一个普通 curry函数会将参数从左到右进行柯里化。
这是我们的实现,以及它在内部使用的另一个实用函数 flip 。
一步一步教你 JavaScript 函数式编程(第一部分)
通过 currying(柯里化) ,我们可以创建一些函数,允许我们创建新的、偏应用的函数,我们可以重用这些函数。在我们这个例子中,我们将使用它来创建一个函数,该函数部分应用 predicate断言函数(fn)来进行过滤列表的操作。
这基本上与手动调用二元的 filter(list, fn)函数一样,进行相同的操作。
一步一步教你 JavaScript 函数式编程(第一部分)
我们可以如下使用它吗?
一步一步教你 JavaScript 函数式编程(第一部分)
哇,可以!最初似乎是很多的工作;但是我们从这个方法中得出的结论是:
使用 currying(柯里化)创建一个通用的,可重用的函数,filterWith ,可以在许多情况下使用它来创建更具体的列表过滤器
每当我们得到一些数据时,都可以懒惰地执行这个新的过滤器。我们不能做到调用 Array.prototype.filter的同时,不使其立即对数据列表执行操作
一个更具声明性的API,有助于可读性和理解
关于 predicate断言函数
我们的 filterWith函数需要一个 predicate断言函数,当给定列表中的某个元素时,它返回true或false,以确定是否应该在新过滤的列表中返回该元素。
让我们从一个更通用的比较函数开始,它可以告诉我们一个给定的数是否大于或等于另一个数。
我们文章的发布日期可以转换成数字,时间戳格式(自Epoch以来的毫秒数)这应该可以正常工作。但是,用于过滤数组的断言函数只能传递一个参数来检查,而不是两个。
那么,在需要一元函数的情况下,如何使我们的二元比较函数工作呢?
Currying(柯里化)可以再次拯救我们!我们将使用它来创建一个函数,该函数可以创建一元比较函数。
var greaterThanOrEqualTo = rightCurry(greaterThanOrEqual);
我们现在可以使用这个柯化版本来创建一个 predicate断言函数,可以用于列表过滤,例如:
棒极了!现在我们回到我们的示例,创建一个 predicate断言函数,具体解决我们原先的过滤掉发布在30天以前的文章了:
到现在为止还挺好!
我们创建了一个可以轻松重用的过滤断言函数。另外,因为我们使用的是函数式方法,所以我们的代码更具声明性,易于遵循 –它的读取方式与工作原理完全相同。可读性和维护是编写任何代码时需要考虑的重要事情!
类型问题…
呃,我们还有另一个问题!我们的程序需要过滤的是一个对象列表,所以我们的 predicate断言函数将需要访问传入的每一项的 published属性。
我们目前的 predicate断言函数,within30Days不能处理对象类型的参数,只能处理具体的数值!让我们用另一个函数来解决这个问题吧!(你在这里看到一个模式了吗?)
我们想重用我们现有的断言函数;但修改其参数,以便它可以与我们的特定对象类型一起使用。这是一个新的实用函数,让我们通过修改其参数来扩展现有的函数。
这是迄今为止最有趣的函数式实用工具函数,并且几乎与 Ramda.js库中相同名称的函数相同。
useWith返回一个修改原来函数(fn)的函数,所以当被调用时,它将通过相应的变换(txnfn)函数传递每个参数。如果在调用时比转换函数有更多的参数,那么剩下的参数将会以 “as is”的形式传递。
让我们用一个小例子来帮助解释这个定义。简单地说,useWith让我们执行以下操作:
当我们调用 additiveSum(4,5)时,我们基本上可以得到以下调用栈:
additiveSum(4,5)
add1(4) => 5
add1(5) => 6
sum(5, 6) => 11
我们可以使用 useWith来修改现有的 predicate断言函数来在对象类型上操作,而不是数值。首先,让我们再次使用 currying(柯里化)来创建一个函数,该函数允许我们创建偏应用的函数,这些函数可以通过属性名访问对象。
现在我们可以使用 getWith作为变换函数,从每个对象获取 .published日期,传递给用于过滤器(filter)的一元断言函数。
我们来试试看一下测试数据:
准备过滤!
好的,鉴于我们的第一个需求是保留最近30天内的文章记录,那么用我们的响应数据来提供一个完整的实现。
最后在对各位刚刚入门前端的程序员提点建议。
对于刚刚参加工作的同学来说,思考比做事更重要。如果你为了业务而业务,不停的去堆积,只能说过些年你还是如此。去好好的想一想,编程到底是在做什么?
如果你现在还准备去学习前端 ,那至少你可以通过社区来了解前端的发展动态,去了解出现了哪些新的框架,更新了哪些新的Api或者属性。未来一段时间内,国内或者国际厂商会使用哪些技术等等。
在此我还是要推荐下我自己的前端学习群:685910553,不管你是小白还是大牛,小编我都挺欢迎,不定期分享干货,包括我自己整理的一份最新的前端资料和零基础入门教程,送给大家,欢迎初学和进阶中的小伙伴