函数式编程

命令式编程

描述计算过程步骤,有变量、赋值、表达式、控制语句;
而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程

它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。
更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。
函数式编程关心数据的映射,命令式编程关心解决问题的步骤
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

一等函数

“一等”这个术语通常来描述值。

  • 存储为变量、数组元素、对象属性值
  • 与数字计算
  • 可以retrun
  • 可以当作参数传递

    作用域与闭包

    词法作用域

    指一个变量的可见性,及其文本表述的模拟值。变量的查找开始于最近的绑定上下文而向外扩展,找到第一个绑定。

    动态作用域

    是根据调用者的上下来返回不同this
    1. function globalThis() { return this}
    2. globalThis();// window
    3. globalThis.call('hehe');// 'hehe'
    4. globalThis.apply('hehe',[]);// 'hehe'

    函数作用域

    在函数体声明的变量,会进行变量提升;
    1. function strangIdentity(n) {
    2. for(var i=0;i<n;i++) {}
    3. console.log(i);
    4. // 实际是
    5. var i;
    6. for(i=0;i<n;i++) {}
    7. }

    闭包

    是一个函数,捕获作用域内的变量;并且不会随着函数执行销毁垃圾回收掉,而是会被保留着。

    高阶函数

    JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

    函数作为参数传递

    通过将参数从值替换函数,可以提供更加灵活的功能。
    1. _.max([1,2,3,4]); // 4
    2. _.max([{name:"fred",age:65},{name:"fred",age:66}],(item) => item.age);// {name:"fred",age:66}

    return 函数

    高阶函数参数可以用来“配置”返回函数的行为。(称为引用透明性)
    1. let add100 = makeAdder(100);
    2. add100(38);// 138

    由函数构建函数

    函数组合

    函数组合,可以将多个函数组合在一起,形成多态函数,根据不同的参数产生不同行为调用。需要有一种接收一个或多个函数,然后不断的尝试调用,直到返回一个非undefined的值。在构建一个应用程序时,需要对参数来确定实现方法是否可以,这是正确的操作。

    柯里化

    把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数;
    1. 参数复用2. 提前返回;3. 延迟计算/运行
    1. function partial(fun) {
    2. var pargs =_.rest(arguments);
    3. return function() {
    4. var args = cat(pargs,_.toArray(arguments));
    5. return fun.apply(fun,arges);
    6. }
    7. }

    函数组合拼接

    1. function isntString(str) {
    2. return !_.isString(str);
    3. }
    4. // 改成 会执行从右到左
    5. var isnString = _.compose(function(x){ return !x},_.isString);

    递归

    是搜索以及处理嵌套数据结构的强大工具。理解递归对函数编程很重要。
  1. 解决方案使用对一个普通问题子集的单一抽象
  2. 可以隐藏可变状态
  3. 是一种实现懒惰和无限大结构的方法

实现递归的关键在于认识到某个值是由一个更大问题的字问题构建出来的。

  1. function myLength(ary) {
  2. _.isEmpty(ary) ?0:1 + myLength(_.rest(ary))
  3. }

编写自递归函数,规则如下

  • 知道什么时候停止
  • 决定怎样算一个步骤
  • 把问题分解成一个步骤和较小问题

    相互关联函数

    两个或多个函数相互调用称为相互递归。可以使用递归克隆对象,递归数组
    1. function evenStevent(n) {
    2. n === ?true:oddJohn(n)
    3. }
    4. function oddJohn(n) {
    5. n === 0?false:evenStevent(n);
    6. }
    请谨慎使用递归,会导致调用stack溢出
    递归函数内部嵌套了对自身的调用,除非等到最内层的函数调用结束,否则外层的所有函数都不会调用结束。通俗地讲,外层函数被卡主了,它要等待所有的内层函数调用完成后,它自己才能调用完成。
    每一层的递归调用都会在栈上分配一块内存,有多少层递归调用就分配多少块相似的内存,所有内存加起来的总和是相当恐怖的,很容易超过栈内存的大小限制,这个时候就会导致程序崩溃。

    纯度、不变性和更改方式

    纯函数一下属性,坚持纯度,不仅仅有助于测试,更能推理。
  1. 结果只能从它的参数值来计算
  2. 不能依赖能被外部操作改变的数据
  3. 不能改变外部状态

    不变性

    复合类型,需要灵活改变;所以就需要每次操作的时候。生成新的对象或者数组。

    控制变化政策

    在JavaScript中,完全不可变变量是不现实;所以需要隔离变化点,增加更改变量的成本;操作变量,都需要声明方法如:.set('1');这样方便在set方法做一些操作,限制。

    基于流的编程

    链式调用

    每调用方法,都反回当前宿主对象的引用。
    其中有惰性链式,把要操作的方法,延迟到最后需要请求值的方法中。

    管道

    管道求值,保持所有方法的调用参数是上一个方法的返回结果,这样连接顺序,一直执行下去。主要还是依靠reduce

    总结

    函数式编程其核心:都以函数为抽象单元。函数编程需要保持一定的纯度(不能操作修改有外部依赖的变量):

  4. 结果只能从它的参数值来计算

  5. 不能依赖能被外部操作改变的数据
  6. 不能改变外部状态

通过以下方式达到

  • 闭包:设置防抖、设置私有变量、方法、保持变量值状态;
  • 递归:主要应用搜索以及处理嵌套数据结构
  • 高阶函数:是其他方法的基础;
  • 柯里化:为了多参函数复用性;使用:callee
  • 函数组合:接收若干个函数作为参数,返回一个新函数。新函数执行时,按照由右向左的顺序依次执行传入compose中的函数,每个函数的执行结果作为为下一个函数的输入,直至最后一个函数的输出作为最终的输出结果。其实主要应用是reduce;调用函数;
  • 链式调用:链式调用的代码更加简洁优雅, 方法间的关联度紧密。
  • 管道数据传递方式:管道是将一群函数链接起来,上一个函数的执行结果作为下一个函数的参数;左到右执行;
  • Mixins:扩展与多重继承一起使用;Mixin 函数不依赖或要求一个基础工厂或构造函数:简单地将任意一个对象传入一个 mixin,就会得到一个增强之后的对象。