React把需要不断重复构建的UI抽象成了组件,它充分利用很多函数式的方法减少了冗余代码。可以说,函数式编程是React的精髓。
那么,到底什么是函数式编程呢?
维基百科给出的解释:
(注意星号*之间的内容。)
函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且**避免使用程序状态以及易变对象**。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受**函数当作输入(引数)和输出(传出值)**。
比起命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。
也就是说,函数式编程和命令式编程最大的区别是:
函数式编程关心数据的映射,而命令式编程关心解决问题的步骤。
下面,为了更清晰的解释,我们首先举一个命令式编程的栗子:
(翻转二叉树的左右子树)
function invertTree(root){
if(root === null){
return root;
}
if(root.left){
invertTree(root.left)
}
if(root.right){
invertTree(root.right)
}
var tmp = root.left;
root.left = root.right;
root.right = tmp;
return root;
}
命令式编程关心的是解题步骤,我们到底应该如何做翻转:
var tmp = root.left;
root.left = root.right;
root.right = tmp;
而对应的函数式编程则为:
function Tree(val,left,right){
this.val = val;
this.left = left;
this.right = right;
}
function invert(node){
if(node === null){
return node
}else{
return new Tree(node.value, invert(node.right), invert(node.left))
}
}
函数式编程关心数据如何映射:也就是如何从旧的树映射到新的树。
其实,函数式编程就是借鉴了数学中的函数:给出一个值,根据这个值,返回一个恰当的值。而这个给出的值,也很可能是一个函数。
介于这样的目的,函数式编程就必须引入一个很重要的概念:高阶函数。
高阶函数:参数为函数或返回值为函数的函数。
高阶函数提供了一种函数级别上的依赖注入机制,也就是,高阶函数的逻辑依赖于作为参数传入的那个函数的逻辑。
另外一个函数式编程且又是高阶的栗子就是js中Array的forEach函数,这个函数可以根据传入的参数(也是一个函数),对数组中的每一个对象进行操作。
另外一个很重要的概念就是递归:因为递归正是描述了我们要解决的问题的定义(而不是如何解决问题)。
但是使用递归有一个很让人头疼的问题:栈溢出。
为了解决这个问题,我们可以考虑采用尾递归的形式:
// 求解斐波那伽数列的尾递归写法
function fib(a,b,n){
if(n==0){
return b
}else{
return fib(b,a+b,n-1)
}
}
解释一下:传入的a和b分别是数列的前两个数。每一次进入函数运算,都向前推进一个数,那么b就变成了计算中的第一个数,而a+b就变成了第二个数。
这就是尾递归。
如果不使用尾递归,那么求解斐波那伽函数的代码应该是:
function fib(n){
if(n==0||n==1){
return 1;
}else{
return fib(n-1)+fib(n-2);
}
}
对比两段代码我们可以发现,尾递归就是不要保持当前递归函数的状态,而把需要保持的东西全部用参数给传到下一个函数里,这样就可以自动清空本次调用的栈空间。这样的话,占用的栈空间就是常数阶的了。
再确切一点,当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。比如f(n, sum) = f(n-1) + value(n) + sum;
会保存n个函数调用堆栈,而使用尾递归f(n, sum) = f(n-1, sum+value(n));
这样则只保留后一个函数堆栈即可,之前的可优化删去。