本文内容提炼自2016年Anjana Vakil在JSConf的演讲,视频地址为Learning Functional Programming with Javascript。虽然当下有很多关于函数式编程的教程了,但这个演讲我觉得是最言简意赅的了。演讲者的表达非常清晰流畅,有条件的朋友强烈推荐看原视频。
什么是函数式编程
函数式编程就是将程序全部使用函数来表达,向函数传入参数,接着输出结果。当有一串数据时,我们需要思考如何通过函数的形式来表达,而不是一行一行的命令式编程,举个例子:
非函数式编程
var name = "Anjana"
var greeting = "Hi i'm"
console.log(greeting + name)
// Hi i'm Anjana
这就是典型的命令式编程,先处理第一行,接着处理第二行。再来看看用函数式来表达这串代码,非常简洁易懂:
function greeting(name) {
return "Hi i'm " + name
}
greeting("Anjana")
纯函数和副作用
函数式编程的核心是使用纯函数(pure function)来避免副作用(pure effects)。纯函数是指,只使用传入的参数来处理输出的结果,这样的好处是每次传入相同的参数,返回的内容都是相同的。副作用是指函数使用了全局变量或者没有返回结果,导致函数不可控。(纯函数关注的是函数内容的处理以及返回的结果,所以函数内部仅仅有console也不能算纯函数,虽然他返回的是void)
function print(){
console.log("test")
}
或
var name = "Anjana"
function getName(){
return name
}
print
和getName
这两个函数都不是纯函数。
高阶函数
函数式编程第二个关键点就是高阶函数,高阶函数是指可以将函数作为参数传入另一个函数,或者在一个函数中返回另一个函数:
function makeAdjectifier(adjective) {
return function(string) {
return ajective + ' ' + string
}
}
var coolifier = makeAdjectifier("cool")
coolifier("conference")
// cool conference
不要使用遍历(no iterate)
在函数式编程中,不要使用for
或者while
循环,而是使用高阶函数比如map
,reduce
,filter
。这里我不会详细介绍关注这三个函数的使用方法,但我会使用一张图来告诉你什么是map
和reduce
:
(这张图真的很形象了,youtube底下的评论也在说这张图形容的非常好,仔细想想确实像这么回事儿)
避免可变数据
在实际开发中,我们经常会遇到改变数据内容的情况:
var rooms = ["H1", "H2", "H3"]
rooms[2] = "H4"
console.log(rooms)
=> ["H1","H2","H4"]
在这个例子中,我们处于各种原因,要修改数组rooms
中的H3
,于是我们直接修改了数组的第二项。要注意的是这样会导致各种各样不可预见的问题,特别是在协作开发的过程中,其它人可能并不知道我们修改了数组,debug起来会非常的头疼。
更好的解决办法是将rooms
变为不可变的数据,这样无论是谁,什么时候使用rooms
,他还是之前的样子。
var rooms = ["H1", "H2", "H3"]
var newRooms = rooms.map(item => {
if(item === "H3") return "H4"
else return item
})
newRooms => ["H1", "H2", "H4"]
rooms => ["H1", "H2", "H3"]
通过这种方式,可以保证rooms
是不可变的,这也会降低代码的BUG率。这时你可能会问,如果我的数据量非常大,这种copy会非常影响性能。
没错,请继续看。
通过持久化数据结构减少不可变数据的性能问题
正如前文所说,如果我有一个很复杂的数据,而我仅仅修改其中一点点数据,我还要重新复制一个数据来处理,是非常影响性能的。而通过持久化数据结构,是可以解决这个问题的,我们来看看它是如何工作的。
还是拿之前的例子,我们复制了rooms,那在内存中实际就有了两个数组:
这好吗?这不好。
所以,持久化数据结构的处理方法是这样的:
将数组的每一项作为一棵树的子节点(node),当我们要修改某一项的时候,只需要创建一个新节点,去连接到这颗树中即可,这样既复用了不需要改变的数据,减少性能的损耗,同时也保证了原始数据的不可变。
当然,在JavaScript中我们也不需要为此早轮子,已经有很多成熟的库供我们使用。比如视频中作者推荐的Mori,不过我更推荐Facebook的immutable.js,因为他在国内使用的人可能会更多些。
至此,视频的大致内容就结束了。实际上,国外很早就开始推函数式编程了,其实一开始我也觉得命令式的编程也挺好,写起来简单易懂,其他人看起来也方便。但就是之前踩过可变数据的坑后(debug痛苦级别最高档),我才知道不可变数据的重要性。所以,如果你也想改变你的代码习惯,从安装immutable.js开始吧!