如何编写高质量的函数 -- 打通任督二脉篇[理论卷]

3月的春风

凡是点进来的老铁都会受到 dva 的卖萌祝福。我会理论结合实践的去阐述:如何运用函数式编程思想去编写高质量的函数。 在这篇文章中,你能够收获一个满意的答案。html

有时候,世间的答案并不重要,重要的是,你如何去看待和相信这份答案。前端

理论卷看完的小伙伴能够点击下面连接,开启实战卷:java

如何编写高质量的函数 -- 打通任督二脉篇[实战卷]react

这部分是写到1万字后加的

嗯,原本打算一篇搞定,但是写着写着就到了 10000 字了。虽然已经较简洁了,可是涉及到的知识有点多,仍是要花字数去阐述清楚的。因而乎,我决定分为上下两篇来完成打通 任督二脉篇 这个篇章。git

如何写这篇文章

这是我编写高质量函数系列的第三篇文章 -- 打通任督二脉。github

前两篇文章分别为:算法

如何编写高质量的函数 -- 敲山震虎篇编程

如何编写高质量的函数 -- 命名/注释/鲁棒篇后端

第三篇是关于 如何运用函数式编程来编写高质量函数 的一篇文章。api

关于函数式编程,我研究了很长时间,这里我想结合如何编写高质量函数,来写出和其余函数式编程文章不同的 feel

本着不重复造轮子,只造更好的轮子的原则,在写这篇文章以前,我思考了一下子,我发现本身面临两个问题,这里我简单说一下,问题和答案以下:

我写文章时面临的问题

第一个问题:如何高质量的阐述函数式编程

第二个问题:如何真正意义上的经过打通函数式的任督二脉来提升编写函数的质量

个人答案

第一个问题的答案

我会经过阐述几个很细节但很是重要的函数式编程知识,经过这些细节来介绍函数式编程,同时向你们展现函数式编程的知识体系,以及和咱们如今大部分的知识体系有什么共同点和不一样点。

第二个问题的答案

这里我会经过分析开源项目的代码和平时工做中的代码以及小伙伴的代码,来展现如何运用函数式编程来提升函数的质量。

上述是我面临的一些问题和解决方案,最后写出了这两篇文章,但愿可以得到你们的喜欢吧。(固然喽,拉勾打个赌,敢不敢不看完文章就先给我点个赞,嘿嘿嘿)

番外篇——当代前端工程师面临的考验

忽然的番外,让我措手不及,来不及思考,就要进入新世界了。

需求的复杂度愈来愈高

我想说的是:因为前端需求的复杂度愈来愈高,大量的业务逻辑放到前端去实现,这虽然减轻了后端的压力,可是却让前端的压力大大增长,前端工程师为此要完成存储、异步、事件等一系列综合性的操做。

需求完成度愈来愈苛刻

咱们能够看一个公式:

f(需求) = 完美

简单理解就是:输入需求,而后输出尽量完美的结果。

在需求复杂度持续增长的条件下,需求完成度却愈来愈苛刻,这样的状况,会致使前端工程师的压力很大。咱们不只要解决各类负责的业务场景,还要保证还原度和开发效率,以及线上运行的性能等方面的表现。

前端工程师的广泛目标

我总结了一下,大体有四点:

  1. 加强开发技能
  2. 提升代码质量
  3. 提升开发效率
  4. 享受生活和 coding 的乐趣

这四点总结起来,咱们要达成的目的,其实就是下面这张图:

我能作的就是提早告诉你 One Piece 的内容。嗯,没错,就是上面这张图。加油吧,各位兄得。

前端工程师的广泛现状

但现实老是美好和残酷混合的,因为各类缘由,前端工程师的广泛现状能够用下面几点概况。

  1. coderoop 思想不强,致使代码质量不高
  2. coderfp 思想不强,致使代码质量不高
  3. JS 语言自身的问题,致使代码的可控性较差

为何我会这样说呢,且听我娓娓道来

首先,JS 是一门很灵活的编程语言,在灵活和基础不牢的双重做用下,代码就会积累足够的复杂度,代码的质量和可控性就会出现问题。

关于 oopfp 的思想,能够用下面两句话进行总结概况

面对对象编程 oop 经过封装变化,使得代码质量更高。

函数式编程 fp 经过最小化变化,使得代码质量更高。

而前端工程师的 oopfp 思想都不是很成熟,虽然如今前端界一直在推进吸取这些思想,但目前确实仍是一个问题。

我我的的见解和总结

对于上面我写的那些话,我不是说前端工程师底子差什么的,我是在客观阐述一些观点,你们能够想一下,为何如今不去说 PHP 了,还不是由于爱你( 前端工程师 )啊。

咱们就像是新世代被选中的一群孩子,能作的就是接受这份挑战。就算之后某个时刻,你不作 coder 了 ,可是如今,身为前端 coder 的你,应该去打败它,不为别的,只为做为 coder 心中的那一份执着和荣耀。

科普篇--关于编程语言

你跟我来,我从番外篇穿梭到科普篇的世界里。

JavaScript 是函数式编程语言吗

要回答这个问题,首先,要明确的一点是:

JS 不是纯正的函数式编程语言,因此它不是函数式编程语言。

为何呢?

由于它具备 for 循环等命令式味道太重的语言设计。真正的纯函数式编程语言,好比 Haskell 是没有 for 循环的,遍历操做都是使用递归。

为何我要提这个点,是由于我想说一个道理,那就是:

既然 JavaScript 不是纯正的函数式编程语言,那咱们就没有必要在使用 JS 的时候,所有采用 FP 思想,咱们要作的,应当是取长补短。其实这句话也从另外一个方面结束了以前掘金上关于函数式编程的讨论。

那 JavaScript 是什么语言?

这里我引用一句话:

天然语言是没有主导范式的,JavaScript 也一样没有。开发者能够从过程式、函数式和面向对象的大杂烩中选择本身须要的,再适时的地把它们融为一体。

出自:Angus Croll, 《If Hemingway Wrote JavaScript》

听我分析,虽然上面的话可能过于绝对,若是想知道上面的话为何会这样说,我建议你们能够去学一下编译原理知识。

为何我要这样建议

是由于我我的认为,全部的高级语言,都是要经过翻译来变成机器语言的,而越底层的实现,其思想和方法越具体和单一。好比,词法分析,语法分析,业界的方法屈指可数。也是由于只有统一,才能创建标准,就像当今的 TCP/IP 协议。

固然,我胡诌那么多没有啥卵用,但咱们依然能够知道并肯定一件事情,那就是:

讨论 JavaScript 是一门基于什么样的语言,是没有任何意义的。

一等公民到底是什么鬼哦

都是 JS 的函数是一等公民,那一等公民到底是什么鬼哦?其实,编程语言也是分 阶级 的,大体有以下阶级:

一等公民

若是编程语言中的一个值,能够做为参数传递,能够从程序中返回,能够赋值给变量,就称它为一等公民。

二等公民

能够做为参数传递,可是不能从子程序中返回,也不能赋给变量。

三等公民 它的值连做为参数传递都不行。

因为 JS 的函数是一等公民,因此咱们能够把函数当初参数,也能够做为返回值,也能够赋值给变量。也正由于是一等公民,JS 的函数式编程才能发光发热。

卖萌篇--函数的分类

嗯,是否是很气,仁兄别急,dva 如今拉着你的手,从科普片场来到了卖萌片场。

下面是我心血来潮,总结加胡诌的一些见解。

从纯洁性角度分类

  1. 纯函数
  2. 非纯函数

嗯,我这分类没谁了,就像人,能够分为是人和不是人...

从调用形式角度分类

中缀函数

代码以下:

6 * 6 
复制代码

6 * 6 中的 * 就是一个函数。并且因为在两个表达式的中间,因此叫中缀。看到这,你们应该有这样的思惟,那就是在 JS 中用这些符号的时候,要天然的将其想象成函数。这样你会发现代码会变得更容易理解。

前缀函数

代码以下:

function f(str) {
  console.log(str)
}

f('hello, i am 源码终结者')
复制代码

这种在函数名 f 后面加上参数的执行形式,就称函数 f 为前缀函数。这个我就不说了,你们应该很熟悉了。

提一下 let

当我学习 haskell 的时候,我发现 haskell 也有 let 。它是这样介绍的:

let 后加上表达式,能够容许你在任何位置定义局部变量。

当看到这,我马上想起来 JS 中 的 let 。这和 JSlet 不谋而合,虽然其余语言也有 let ,好比 go 语言。但我如今更有信心肯定, JSlet 的诞生,借鉴的应该是 haskell 中的 let

因此,在 JS 中,我更想将 let 称为绑定。haskell 中介绍了 let 的缺点,这里咱们推一下能够知道,JS 中的 let 缺点也很明显,那就是 let 绑定的做用域被限制的很小。

因此你会发现,在某些场景下,使用 var 也是很是合理和正确的。

我是分割线,热身要结束了,即将开始正题。


文章总体思路

当失去之后,再次拥有时,才会倍加珍惜吧。

嗯,那开始正题吧。

首先能够知道的一点是:本文的思路是清晰的,文章前半段,我会着重阐述函数式编程的理论知识。好比我会阐述不少人心中对于函数式编程的一些困惑。文章后半段,我会结合开源项目的源码和实际工做中的 coding ,来展现如何运用函数式编程来编写高质量的函数。

下文的 函数式编程 统称为 FP

关于 FP 你真正困惑的那些点--FP理论知识

关于理论知识,我不会把 FP 的方方面面都涵盖,我会尝试从小的点出发,带你去认识 FP ,让你们能够在宏观层面上对函数式编程有一个较清晰的认知。

下面,我会从如下这些点出发:

  • 函数式编程存在的意义
  • 声明式编程
  • 数学中的函数
  • 纯函数/纯洁性/等幂性/引用透明/
  • 反作用
  • 数据的不可变性
  • 惰性
  • 态射
  • 高阶性
  • 其余理论知识

PS: 这里我不会对单个点进行详细介绍,只说出我我的对其的一个认知和理解。

函数式编程存在的意义

这是一本书中的解释:

函数式编程的目的是使用函数来抽象做用在数据之上的控制流与操做,从而在系统中消除反作用并减小对状态的改变。

这句话总结的很不错,看起来好像很深奥,小伙伴们不要怕,看下面个人分析你就明白了。

咱们看一下上面的解释,会发现这句话,实际上是在说:

函数式编程是一种解决问题的方式。

为何我会这样说呢,继续往下看:

咱们继续分析上面的解释,能够知道下面两点:

第一点:FP 抽象了过程,好比控制流(如 for 循环)

第二点:FP 消除程序中的反作用(后面说)

稍加推导能够知道第三点:

FP 的存在必定是解决了其余范式的一些缺陷,好比面对对象范式的一些缺陷,如反作用在面对对象中很常见,从 Java 的锁机制能够看出反作用的表现,固然我对锁机制这种观点可能不许备,可是关于必定是解决了其余范式的一些缺陷这个观点,仍是很正确的,否则就没有存在的意义了。

提一下函数式编程和抽象的关系

上面第一点提到,FP 抽象了过程,关于抽象,咱们知道,抽象层次越低,代码重用的难度就会越大,出现错 误的可能性就会越大。

为何说抽象层次越低,代码重用的难度就会越呢?

我来举个栗子,你就会明白了。就一句话:每次吃面条时,做为 coder 的你,有两种选择:

第一种:直接去超市买把面条,下着吃。

第二种:本身和面,本身擀面条,而后下着吃。

上面两种方法,你会选哪种呢,其实我已经猜到了,抽象的高和低就是第一种和第二种的区别,小伙伴好好想想。

综上,咱们能够知道:

FP 的本质意义就在于此,真正理解了其存在的目的,也就理解了咱们为何要使用函数式编程了。

前段日子掘金上关于 FP 的讨论

以前掘金上有那么几天很是热闹,是关于函数式编程的讨论,有说函数式好用的,有说不用函数式的政治正确的,有 /^驳/ 的。那些天,天天刷掘金,都能看到相关的文章,老是想说些什么,可是最后也没有说什么。

可是,若是真正理解了函数式编程的意义,也就不会有这么多不一样的声音了。

但声音这么多,主要缘由应该仍是:

写文章的时候,偶尔会说出很绝对的话,这会致使其余做者进行回应,紧接着,事件持续发酵,激起了可爱的吃瓜群众的兴趣,遂开启了大规模的围观和讨论。

在这里,我想用一句话来结束争议,那就是:

函数式不是工具,也不是 api ,它是一种解决问题的方式。

这句话,不作解释了,自行脑补吧。

声明式编程

说到声明式,就要提到命令式。声明式和命令式,是目前前端所面临的两种编程范式。

小伙伴应该知道,函数式编程是声明式编程。当你看完上面我对 FP 意义的解释后,你就会明白为何会有声明式编程存在了。

FP 能够将控制流抽象掉,不要其暴露在程序中,这就是一种声明式编程。

关于编程范式,你们能够快速浏览下面关于编程范式的维基:

[维基百科连接-编程范式]zh.wikipedia.org/wiki/编程范型

思考题:声明式编程的软肋在哪,它与命令式编程的本质区别是什么?小伙伴能够思考一下。

数学中的函数

为何要说这个,我认为,若是想更好的理解和运用函数式编程,就必需要去了解数学中的函数,由于函数式编程的不少概念和数学中函数的思想是强相关的。

这里我不作分析了,我贴一个维基连接:

函数-维基

小伙伴们自行学习一下,看的时候,你们能够顺带滑到最下面,看看有相关范畴论的介绍,范畴学的思想对函数式编程很是重要。

纯函数/纯洁性/等幂性/引用透明性

这几个词在 FP 中很常见,在我看来,这些词最终的目标都是同样的,那就是达成上文提到的 FP 的目的。这里我用简洁的语言去阐述一下这些词背后的理念。

这些词表明的意识实际上是同样的,能够这样理解:

纯函数的特性包括纯洁性,等幂性,引用透明性。

那纯函数是什么呢?能够经过下面的说法来认识纯函数。

全部的函数对于相同的输入都将返回相同的输出。

PS: 固然我这篇文章并非让你们运用纯函数去实现编程,而是想让你们运用函数式的思想去实现编程,不必定要是纯函数的,引纯函数知识 FP 的一个思想而已。

函数的反作用

上面说了纯函数,那么如何理解函数的反作用呢,能够这样理解:

相同的输入,必定会输出相同的输出,若是没有的话,那就说明函数是有反作用的。

从这句话,咱们认真想一下,能够推导出:

由于 I/O 的输出是具备不肯定性的,因此一切的 I/O 都是有反作用的。

如何处理反作用

后面实战再说,这里我提一点,你们能够去看看 dva 的文档或者源码,其中关于异步请求的,都只在 effect 中完成,而 effect 在函数式编程中有反作用的意识,这是一种将函数的反作用隔离起来的设计思想。

数据的不可变性

关于数据的不可变性,仍是很重要的,咱们须要去深刻的理解它。我想几个问题:

第一:前端出现数据的不可变性的目的

第二:前端如何作到数据的不可变性

下面咱们来一次回答这两个问题。

前端出现数据的不可变性的目的

回答这个问题以前,我想说另一个事情,那就是:许多编程语言都支持让数据不可变,好比 javafinal ,能够看看这篇文章,很不错。看完,你会对数据不可变有一个交叉的了解。

浅谈Java中的final关键字

ok ,我我的认为,不可变性思想很简单,但很重要,我举个例子你就明白了。

代码以下:

const obj = {}
const arr = []
const sum = 0

function main(arg){
  // TODO:
}

main(obj)
main(arr)
main(sum)
复制代码

从上面的代码,咱们能够知道如下几点:

第一点:main 函数的参数都是依赖了外部做用域的变量

第二点:变量 obj arr sum 是能够被外界改变的

第三点:要注意一个事情,咱们目前没法避免外界对这些变量进行改变,有多是必需要改变的,也有多是无心间被改变了。

第四点:虽然咱们没法避免外界对其可能形成的影响,但咱们能够避免咱们本身对其可能形成的影响,好比咱们使用 main 直接对 obj arr 自己进行操做,这样的话就会使得 objarr 被改变,若是改变了,那在咱们屡次调用 main 函数时,就可能会出现问题。

第五点:上面的代码已经违反了函数式编程的原则,就是不能依赖外界的数据,这样会有反作用。

基于上面五点后的答案

知道上面五点的信息后,咱们想一下,若是能把arr obj sum 设置成不可变数据结构。那咱们就能够直接避免了外界和咱们本人对其形成的全部影响。由于设置了数据不可变性,外界无法去改变它们。因此这就是为何,前端会出现数据不可变性这一说,由于当前端愈来愈复杂的时候,这种要求就表现的更加明显了。可是,很遗憾的时候,JS 是不支持不可变的数据结构的。

前端如何作到数据的不可变性

前端如何作到数据的不可变性,这里有三种方法:

第一种方法:

在函数内对引用类型建立一个新的副本,而后对副本进行指定业务处理。

第二种方法:

对引用类型进行封装,业界经常使用的方法就是值对象模式,经过闭包的形式暴露出去。

第三种方法:

经过 JSapi 来实现,用规则来阻止对象状态的修改。好比使用 Object.freeze() 来冻结一个对象,当外界试图对这个对象进行增删改属性的时候,就会致使程序错误。对于第三个方法,咱们看一下 react 的源码中,哪些地方用到了,如图所示:

ReactDOMComponent.js 中对 nextProp 对象进行了冻结,可能不少人不明白为何要冻结,其实很好理解,咱们从因果关系来理解。

首先 react 假设 nextProp 不会发生改变,那么怎么保证不会发生改变呢?确定是经过 Object.freeze(nextProp)nextProp 进行冻结,冻结后,react 就能够知道,从这个时刻开始,nextProp 对象就是不可变的了。因此它就能够进行下一步,按照不可变做为前置条件的流程了。这样作,其实也是在告诉咱们,若是咱们试图修改它,就会报错。

数据的不可变性总结

理解不可变性是很是重要的,咱们不能把不可变性框在一个很窄的范围,好比上面的代码,全局变量 sum 虽然是值类型,可能你以为值类型是不可变的,可是你会发现,它也会被外界所修改。因此这里的不可变性,并非小范围的不可变,而是多个层面的不可变,这里还须要多多去理解和感悟吧。

惰性

我认为惰性不只仅是 FP 的特性,它在前端领域,也是一个很是重要的思想。

惰性能够用一句话来解释:

只在须要时才发生。

怎么理解这句话呢,我来列举一下,前端用到 惰性思想 的场景,场景以下:

  • 前端的懒加载
  • 前端的 tree shaking
  • 无限列表问题
  • recycle list
  • 动态导入导出
  • 前端的缓存

想一下,会发现, FP 的惰性目的,已经包含在上面那些场景中了。

关于 只在须要时才发生。 这句话, 小伙伴们能够结合我列举的场景进行对比理解,我就不继续苍白解释了,点到为止。

态射

为何我要说这个,是由于理解态射的知识,对你更好的使用函数式编程有着很是重要的意义。

那什么是态射呢?

简单理解,就是一种状态到另外一种状态的映射。态射包含不少种形式,这里咱们说一下类型映射。好比输入一个 String 类型,而后返回一个 Boolean 类型,这就属于一种态射。

好比下面的 demo

const isNumber = n => typeof n === 'number'
复制代码

上面的函数,输入的是数字类型,出入的是布尔类型,这就是一种态射形式。函数式编程的不少概念都和态射有关系,好比管道化、高阶化、组合、链式等。如何将一种类型的数据机构映射成另外一种数据结构,这是一个很重要的知识点,这里我把核心的点提了出来,小伙伴本身必定要多去了解和掌握。

高阶性

高阶性是函数式编程的一个核心思想之一,在更高抽象的实现上,必须依靠高阶性来实现。经过把函数做为参数,对当前函数注入解决特定任务的特定行为,来代替充斥着临时变量与反作用的传统循环结构,从而减小所要维护以及可能出错的代码。

我认为,高阶性是一个很是重要的特性,想玩转函数式编程,就必需要玩转高阶性。为

为何我要这样说呢?

由于你会发现,函数的管道化,组合,柯里化,都是要依靠函数的高阶性来实现。拥有高阶性的函数,咱们称它是高阶函数,那么什么样的函数成为高阶函数呢。

目前我理解的高阶函数---HOC(Higher-Order-Functions)的定义是这样的:

传入的参数是函数,返回的结果是一个函数,这两个条件,具有一个,就能够称函数是高阶函数。

关于高阶函数,更可能是去理解和实战,这里关于理论方面的知识我就很少介绍了,你们结合个人实战篇好好理解一下。

其余理论知识

上面我提到的理论知识,都是原子级别的特性,也算是 FP 的基石。像柯里化、偏应用、函数组合、函子等其余更高阶的实现,都是在前面这些 基石的基础上实现的。因此这里理论篇我就不介绍他们了,小伙伴们自行去分析和学习,我会在实战篇把这些高级实现串起来。固然若是对这方面理论知识有兴趣的小伙伴,想和我交流的,也能够私聊我。

关于 FP 理论知识的总结

FP 是个好东西,它能够帮助你控制代码的复杂性,能够加强你的逻辑能力,可让测试更容易,等等。

如今,你们对比下面两个问题:

  1. 学习算法对前端工程师有用吗
  2. 学习函数式编程对前端工程师有用吗

你会发现,都有用,但不表明咱们就要在实际前端场景中大量使用它们。

我的认为前端须要掌握 FP 的水平

上面我介绍了我认为目前 FP 领域内,须要掌握的一些元知识,只有掌握了这些元知识,咱们才能更好更快的掌握基于元知识衍生出的各类功能实现。好比柯里化函数、偏应用函数、组合函数、高阶函数甚至函子等。

但其实,在我实践了 FP 后,我我的认为,如今的前端工程师在 FP 方面,只须要把组合,柯里化,和高阶玩好就好了。其余的高级用法能够先不用进行实践。我认为前端工程师掌握这三个,就已经可以在前端把函数式思想发挥到淋漓尽致的地步了。这是在学习 FP 的感悟。

文末总结

原本想写一篇的,可是发现一篇太长了,就分红了上下两篇。

函数式编程这块,涉及到的东西很是多,光理论知识就不少了,个人这篇文章对于理论知识,没有过多的去解释和分析,我是按照我我的的理解和总结去向你们展现如何搞定函数式编程的理论知识。因此可能会有一些意见的不统一,或者理论知识涵盖的不全,还请多多理解和支持。这篇文章不是单纯的只分享如何搞定 FP ,是结合如何编写高质量的函数去向你们进行综合阐述。

参考

参考连接

参考书籍

  • JavaScript ES6 函数式编程入门经典
  • JavaScript 函数式编程指南
  • Haskell 趣学指南
  • 其余电子书

交流

如何编写高质量函数系列文章以下(不包含本篇):

能够关注个人掘金博客或者 github 来获取后续的系列文章更新通知。掘金系列技术文章汇总以下,以为不错的话,点个 star 鼓励一下。

github.com/godkun/blog

我是源码终结者,欢迎技术交流。

也能够进 前端狂想录群 你们一块儿头脑风暴。有想加的,由于人满了,能够先加我好友,我来邀请你进群。

风之语

最后:尊重原创,转载请注明出处哈😋

相关文章
相关标签/搜索