js中的函数编程

以前在网上看到了一篇教你如何用js写出装逼的代码。javascript

通过学些以及扩展颇有收获在这里记录一下。java

原文章找不到了。因此就不在这附上连接了。编程

你们看下下面两段js代码。设计模式

上面两端代码效果是如出一辙的,都是在一个指定的数组中,找到指定的数字所在的下标。第一个大多数人都看得懂。第二个就不必定了。数组

 

这里就带你们从一步步最初的版本演化到最终的函数式的版本。函数式编程

但愿你们之后再遇到如此难以阅读的代码,知道怎么去理解。函数

 

为了从上面上面变到下面,咱们须要了解如下知识。性能

箭头函数优化

三元运算符(不讲)设计

尾递归优化

匿名函数(不讲)

柯里化

高阶函数

 

其中,尾递归优化,和柯里化,高阶函数,都是函数式编程里面的东西。因此有必要先简单介绍一下什么是函数式编程。

 

这里先引用下知乎的图。

 

上面的图并非说,最下面的最高级,而是越靠下面的越费脑子。

 

什么是函数式编程?

事实上函数式编程是从范畴论(category theory)发展过来的。而范畴论其实和微积分,数理逻辑,等等都是一种数学理论。而函数式编程只是参考这种思想发展过来的,就像设计模式最初是来源于建筑学同样。

 

为了更好的理解函数式编程,这里也再简单的介绍下范畴论。高深的我也不懂。。。

在维基百科里面是这么定义的

 

也就是说只要是存在某种关系,能够从一个对象转化为另一个对象,那么对象和它们之间的关系就构成一个范畴。

下面的图是一个示意图。

 

 

红色的点和黄色的箭头在一块儿就构成一个范畴。

箭头arrow,还有一个正式的名字叫作态射(morphism)。范畴论认为,同一个范畴的全部成员,就是不一样状态的"变形"(transformation)。经过"态射",一个成员能够变造成另外一个成员。

 

上面的很抽象,咱们举例子说明下。

你们都对面向对象的思想比较了解,在面向对象的思想里,万事万物都是对象,而对象又能够抽象成为类。好比,黄种人,白种人,黑人。都是对象,能够抽象为人这个类。这是他们有共性。可是他们之间并不存在转化关系,黑人不可能转化为白种人,就算他整容整的很白,概念上他仍是个黑人。

而数字1,2,3,4,5。。。他们之间存在必定的关系,1+1能够变成2,2+1可变成3,2+2又能够变成4。咱们能够认为,数字和它们之间的态射就是一个范畴。每个数字,经过一个态势能够变为另一个数字。

 

因此范畴包含两部分

  1. 成员
  2. 关系

而过分到程序里面就是

  1. 函数

简单的来讲就是,一个值能够经过一个函数变为另一个值。因此函数式编程要求每个函数必须是干净的,进入一个值,出去另一个值。不会操做任何方法外的数据。

 

函数式编程有两个最基本的运算。

  1. 合成

 

合成的概念就是若是一个值须要通过多个函数才能变成另一个值,就能够把两个函数合成一个函数。好比1,须要通过add1和add2才能变成4,那么就能够合成一个add3出来。

假设add1=f,add2=g

上面看起来函数有点多,那咱们把add1和add2都给匿名了,看起来会好一点

函数的合成还要知足结合律

假设f,g,h分别是add1,add2,add3.

那么(h·g).f 就是 add3(add1(add2))  而h·(g·f) 就是add1(add2(add3))

他们二者应该是相等的。

 

2.柯里化

f(x)和g(x)合成为f(g(x)),有一个隐藏的前提,就是f和g都只能接受一个参数。若是能够接受多个参数,好比f(x, y)和g(a, b, c),函数合成就很是麻烦。

这时就须要函数柯里化了。所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。

这里解释一下,柯里化以后是采用的js里面的链式调用。AddX(2)其实返回的是function(x)

{return x+2;} 这时候再跟一个(1),就是把1传入里面执行了。

 

固然函数式编程还有不少别的东西,这里就不一一介绍了,有兴趣的的能够本身查下。

 

下面说下, 尾递归优化

咱们知道递归的害处,那就是若是递归很深的话,stack受不了,并会致使性能大幅度降低。因此,咱们使用尾递归优化技术——每次递归时都会重用stack,这样一来可以提高性能,固然,这须要语言或编译器的支持。Java 就不支持,可是javascript支持。而所谓的支持,就是说编译器会自动优化,对于尾递归的代码会自动优化成。

普通递归。

下面是入栈和出栈的过程。会保存上一步的计算状态,太深的话就会栈溢出。

fac(5)

(5*fac(4))

(5*(4*fac(3)))

(5*(4*(3*fac(2))))

(5*(4*(3*(2*fac(1)))))

(5*(4*(3*2)))

(5*(4*(6)))

(5*24)

120

尾递归

当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归

每次都是执行一个独立的函数,和以前的函数并无关联。

fac(5,1)

fac(4,5)

fac(3,20)

fac(2,60)

fac(1,120)

120

 

普通递归建立stack累积然后计算收缩,尾递归只会占用恒量的内存。只须要保存每次计算出来的值,而后传入同一个函数就好。

高阶函数。

高阶函数就是函数当参数,把传入的函数作一个封装,而后返回这个封装函数。上面一直都在用到。

 

箭头函数

ECMAScript2015 引入的箭头表达式。箭头函数其实都是匿名函数。简单的理解就是经过箭头建立函数。

一个参数时候能够省略小括号,方法体只有一句的时候能够省略大括号

Function (x){return x+1}; 等价于(x) =>{return x+1}或者x=>return x+1;

无参数必须有括号。

Function (){return 1+1} 等价于 ()=>{return 1+1}

若是想加名字的话。

Var add = ()=>{return 1+1}; 调用 add() 就会返回2

 

该介绍的都介绍了,下面就一步步改造。

原始版本

 

尾递归优化以后

替换三元运算符

 

函数体内的函数参数化

转化为箭头函数

匿名

引入高阶函数,并柯里化。

为了方便调用再加上名字

下面断点图帮助理解。

相关文章
相关标签/搜索