开始学习函数式编程

1,434 阅读4分钟

本文内容提炼自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
}

printgetName这两个函数都不是纯函数。

高阶函数

函数式编程第二个关键点就是高阶函数,高阶函数是指可以将函数作为参数传入另一个函数,或者在一个函数中返回另一个函数:

function makeAdjectifier(adjective) {
	return function(string) {
    	return ajective + ' ' + string
    }
}

var coolifier = makeAdjectifier("cool")
coolifier("conference")

// cool conference

不要使用遍历(no iterate)

在函数式编程中,不要使用for或者while循环,而是使用高阶函数比如mapreducefilter。这里我不会详细介绍关注这三个函数的使用方法,但我会使用一张图来告诉你什么是mapreduce

(这张图真的很形象了,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开始吧!