作者:Fernando Doglio 译者:前端小智 来源:medium 移动端阅读:点这里
点赞再看,微信搜索 【大迁世界】 关注这个没有大厂背景,但有着一股向上积极心态人。本文 GitHub
https://github.com/qq449245884/xiaozhi 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。
大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】。
面向对象编程和函数式编程是两种非常不同的编程范式,它们有自己的规则和优缺点。
但是,JavaScript 并没有一直遵循一个规则,而是正好处于这两个规则的中间,它提供了普通OOP语言的一些方面,比如类、对象、继承等等。但与此同时,它还为你提供了函数编程的一些概念,比如高阶函数以及组合它们的能力。
高阶函数我们行人人三个概念中最重要的一个开始:高阶函数。
高阶函数意味着函数不仅仅是一个可以从代码中定义和调用,实际上,你可以将它们用作可分配的实体。如果你使用过一些JavaScript,那么这并不奇怪。将匿名函数分配给常量,这样的事情非常常见。
const adder = (a, b) => {
return a + b
}
上述逻辑在许多其他语言中是无效的,能够像分配整数一样分配函数是一个非常有用的工具,实际上,本文涵盖的大多数主题都是该函数的副产品。
高阶函数的好处:封装行为有了高阶函数,我们不仅可以像上面那样分配函数,还可以在函数调用时将它们作为参数传递。这为创建一常动态的代码基打开了大门,在这个代码基础上,可以直接将复杂行为作为参数传递来重用它。
想象一下,在纯面向对象的环境中工作,你想扩展类的功能,以完成任务。 在这种情况下,你可能会使用继承,方法是将该实现逻辑封装在一个抽象类中,然后将其扩展为一组实现类。 这是一种完美的 OOP 行为,并且行之有效,我们:
- 创建了一个抽象结构来封装我们的可重用逻辑
- 创建了二级构造
- 我们重用的原有的类,并扩展了它
现在,我们想要的是重用逻辑,我们可以简单地将可重用逻辑提取到函数中,然后将该函数作为参数传递给任何其他函数,这种方法,可以少省去一些创建“样板”过程,因为,我们只是在创建函数。
下面的代码显示了如何在 OOP 中重用程序逻辑。
//Encapsulated behavior封装行为stract class LogFormatter {
format(msg) {
return Date.now() + "::" + msg
}
}
//重用行为
class ConsoleLogger extends LogFormatter {
log(msg) {
console.log(this.format(msg))
}
}
class FileLogger extends LogFormatter {
log(msg) {
writeToFileSync(this.logFile, this.format(msg))
}
}
第二个示是将逻辑提取到函数中,我们可以混合匹配轻松创建所需的内容。 你可以继续添加更多格式和编写功能,然后只需将它们与一行代码混合在一起即可:
// 泛型行为抽象
function format(msg) {
return Date.now() + "::" + msg
}
function consoleWriter(msg) {
console.log(msg)
}
function fileWriter(msg) {
let logFile = "logfile.log"
writeToFileSync(logFile, msg)
}
function logger(output, format) {
return msg => {
output(format(msg))
}
}
// 通过组合函数来使用它
const consoleLogger = logger(consoleWriter, format)
const fileLogger = logger(fileWriter, format)
这两种方法都有优点,而且都非常有效,没有谁最优。这里只是展示这种方法的灵活性,我们有能力通过 行为(即函数)作为参数,就好像它们是基本类型(如整数或字符串)一样。
高阶函数的好处:简洁代码对于这个好处,一个很好的例子就是Array
方法,例如forEach
,map
,reduce
等等。 在非函数式编程语言(例如C)中,对数组元素进行迭代并对其进行转换需要使用for
循环或某些其他循环结构。 这就要求我们以指定方式编写代码,就是需求描述循环发生的过程。
let myArray = [1,2,3,4]
let transformedArray = []
for(let i = 0; i < myArray.length; i++) {
transformedArray.push(myArray[i] * 2)
}
上面的代码主要做了:
- 声明一个新变量
i
,该变量将用作myArray
的索引,其值的范围为0
到myArray
的长度 - 对于
i
的每个值,将myArray
的值在i
的位置相乘,并将其添加到transformedArray
数组中。
这种方法很有效,而且相对容易理解,然而,这种逻辑的复杂性会随着项目的复杂程度上升而上升,认知负荷也会随之增加。但是,像下面这种方式就更容易阅读:
const double = x => x * 2;
let myArray = [1,2,3,4];
let transformedArray = myArray.map(double);
与第一种方式相比,这种方式更容易阅读,而且由于逻辑隐藏在两个函数(map
和double
)中,因此你不必担心了解它们的工作原理。 你也可以在第一个示例中将乘法逻辑隐藏在函数内部,但是遍历逻辑必须存在,这就增加了一些不必要的阅读阻碍。
函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。我们来看个例子:
function adder(a, b) {
return a + b
}
// 变成
const add10 = x => adder(a, 10)
现在,如果你要做的就是将10
添加到一系列值中,则可以调用add10
而不是每次都使用相同的第二个参数调用adder
。 这个事例看起来比较蠢,但它是体现了 柯里化
的理想。
你可以将柯里化视为函数式编程的继承,然后按照这种思路再回到logger
的示例,可以得到以下内容:
function log(msg, msgPrefix, output) {
output(msgPrefix + msg)
}
function consoleOutput(msg) {
console.log(msg)
}
function fileOutput(msg) {
let filename = "mylogs.log"
writeFileSync(msg, filename)
}
const logger = msg => log(msg, ">>", consoleOutput);
const fileLogger = msg => log(msg, "::", fileOutput);
log
的函数需要三个参数,而我们将其引入仅需要一个参数的专用版本中,因为其他两个参数已由我们选择。
注意,这里将log
函数视为抽象类,只是因为在我的示例中,不想直接使用它,但是这样做是没有限制的,因为这只是一个普通的函数。 如果我们使用的是类,则将无法直接实例化它。
函数组合就是组合两到多个函数来生成一个新函数的过程。将函数组合在一起,就像将一连串管道扣合在一起,让数据流过一样。
在计算机科学中,函数组合是将简单函数组合成更复杂函数的一种行为或机制。就像数学中通常的函数组成一样,每个函数的结果作为下一个函数的参数传递,而最后一个函数的结果是整个函数的结果。
这是来自维基百科的函数组合的定义,粗体部分是比较关键的部分。使用柯里化时,就没有该限制,我们可以轻松使用预设的函数参数。
代码重用听起来很棒,但是实现起来很难。如果代码业务性过于具体,就很难重用它。如时代码太过通用简单,又很少人使用。所以我们需要平衡两者,一种制作更小的、可重用的部件的方法,我们可以将其作为构建块来构建更复杂的功能。
在函数式编程中,函数是我们的构建块。每个函数都有各自的功能,然后我们把需要的功能(函数)组合起来完成我们的需求,这种方式有点像乐高的积木,在编程中我们称为 组合函数。
看下以下两个函数:
var add10 = function(value) {
return value + 10;
};
var mult5 = function(value) {
return value * 5;
};
上面写法有点冗长了,我们用箭头函数改写一下:
var add10 = value => value + 10;
var mult5 = value => value * 5;
现在我们需要有个函数将传入的参数先加上 10 ,然后在乘以 5, 如下:
现在我们需要有个函数将传入的参数先加上 10 ,然后在乘以 5, 如下:
var mult5AfterAdd10 = value => 5 * (value + 10)
尽管这是一个非常简单的例子,但仍然不想从头编写这个函数。首先,这里可能会犯一个错误,比如忘记括号。第二,我们已经有了一个加 10 的函数 add10 和一个乘以 5 的函数 mult5 ,所以这里我们就在写已经重复的代码了。
使用函数 add10,mult5 来重构 mult5AfterAdd10 :
var mult5AfterAdd10 = value => mult5(add10(value));
我们只是使用现有的函数来创建 mult5AfterAdd10,但是还有更好的方法。
在数学中, f ∘ g 是函数组合,叫作“f 由 g 组合”,或者更常见的是 “f after g”。 因此 (f ∘ g)(x) 等效于f(g(x)) 表示调用 g 之后调用 f。
在我们的例子中,我们有 mult5 ∘ add10 或 “add10 after mult5”,因此我们的函数的名称叫做 mult5AfterAdd10。由于Javascript本身不做函数组合,看看 Elm 是怎么写的:
add10 value =
value + 10
mult5 value =
value * 5
mult5AfterAdd10 value =
(mult5
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?