《JavaScript函数式编程》读书笔记

JavaScript是一门很神奇的语言,做为一门现代化的语言,他有不少颇有特点的东西,这些东西,让咱们看到了一个十分自由化的将来,你永远都不知道,本身是否是掌握了这门奇葩的要命的语言。本文,可能没有那么多高深的编程技巧,有的更多的是,对编程自己的理解。由于,不知道本身有多白痴,因此,要记录下来,等到本身不白痴的时候,就能缅怀当年那个白痴的本身了。javascript

什么是函数式编程

所谓函数式编程,是把函数自己上升到一等公民的地位,进行编程构建。在书中,做者用了这么一句话来形容函数式编程:php

函数式编程经过使用函数来将值转换成抽象单元,接着用于构建软件系统。html

额,那么好,咱们先回忆一下什么叫函数。java

函数

通常的,在一个变化过程当中,有两个变量x、y,若是给定一个x值,相应的就肯定惟一的一个y,那么就称y是x的函数,其中x是自变量,y是因变量,x的取值范围叫作这个函数的定义域,相应y的取值范围叫作函数的值域。jquery

这是数学中的定义,简单的说,函数就是从A到B的关系映射。在计算机中,咱们将多条语句组成的程序段(程序块)叫作函数,一个函数自己应该有必定的意义。和数学定义至关的是,变量的生命周期所在的函数空间,为变量的定义域。算法

面向函数的编程

所谓函数式编程,咱们又能够叫作是面向函数的编程。所谓面向函数就是使用函数来做为咱们分析和抽象程序的主要工具。编程

嗯,首先,咱们继续来复习一下json

  • 什么叫作面向过程。数组

“面向过程”(Procedure Oriented)是一种以过程为中心的编程思想。“面向过程”也可称之为“面向记录”编程思想,他们不支持丰富的“面向对象”特性(好比继承、多态),而且它们不容许混合持久化状态和域逻辑。promise

其实,说白了,就是想到什么写什么。

  • 什么叫作面向对象

按人们认识客观世界的系统思惟方式,采用基于对象(实体)的概念创建模型,模拟客观世界分析、设计、实现软件的办法。经过面向对象的理念使计算机软件系统能与现实世界中的系统一一对应。

在面向对象中,咱们都知道对象是两个很重要的概念。

咱们知道,所谓的类,其实就是:

具备相同特性(数据元素)和行为(功能)的对象的抽象就是类。所以,对象的抽象是类,类的具体化就是对象,也能够说类的实例是对象,类实际上就是一种数据类型。

而咱们所说的对象,其实就是:

对象是人们要进行研究的任何事物,从最简单的整数到复杂的飞机等都可看做对象,它不只能表示具体的事物,还能表示抽象的规则、计划或事件。

咱们不难发现,类和对象,其实都是从数据角度出发来思考和解决问题,以数据自己为运算核心来抽象咱们的计算行为。可是,不少时候,咱们会发现,其实咱们的运算行为远远比数据自己要复杂,并且,咱们不少时候,其实并不能很好的去抽象一个对象。

个人数据老师曾经这样教导咱们:

所谓程序,就是数据结构加算法。

若是说,面向对象是从数据结构的角度出发的话,面向函数的编程,就是从算法角度出发,也就是从行为的角度出发。

为何要用函数式编程

数据和行为的关系

在计算机中,数据多数指的是存储结构。行为指的多数是计算操做。好比说这段代码:

function say(){
        let a = 1 + 1; 
        console.log(a)
    }

这段代码里,做为变量存在的asay,是咱们所熟知的数据,而function say()则是包含了整个说的行为。
在面向对象的编程中,咱们习惯把对象做为行为的核心,也就是说,先有人,而后,人来执行一个动做。而,对象,其实就是某一种变量,亦或是某一种数据类型。而函数式编程中,则认为数据只是行为加工的产品。将行为和数据分离。咱们来看一段代码。

// man.php
    class Man {
        function __constructor($sexy){
            $this->sexy = $sexy;
        }
        public function sayHello($string){
            echo "I'm a ".$this->sexy.",I want to say:".$string;
        }
    }
    // male.php
    require_once 'man.php'
    $male = new Man("man");
    $male->sayHello("my name is homker");
    // I'm a man, I want to say: my name is homker

tips:
由于javascript自己是没有类的概念的,为了更好的说明问题,这里使用了php来做为范例语言,固然,你也可使用javascript面向对象的方式来从新实现这段代码。就像这样。

function Man(sexy){
       var self = this;
       self._sexy = sexy;
       self.sayHello = function(string){
           console.log("I'm a "+self._sexy+",I want to say:"+string);
       }
   }

   var male = new Man("man");
   male.sayHello('my name is homker');

这是一段很简单的面向对象的代码,咱们看看一样的功能在函数式中要怎么作。

function Man(sexy){
        return function(string){
            console.log("I'm a "+sexy+",I want to say:"+string);
        }
    }
    
    var sayHello = Man('man');
    sayHello('my name is homker');

咱们会发现,在函数式编程中,咱们去除掉了主语。你不知道这个动做是由谁发出的。相比于在面向对象编程中,数据是对象的属性,在函数式编程中,咱们并不在意这个数据的内容是什么,而更在意其变化。

额,固然,严格意义上来讲,其实,这个sayHello的原型是Object,在浏览器端,追溯他的原型链,它是挂在window对象下面的。

专一于过程自己

在实际的开发过程当中,咱们有的时候很难抽象出一个对象来描述咱们到底要作什么,或者说,咱们其实并不在意这堆数据里面的内容是什么,咱们要关心的,只是把数据通过加工,得出结果,仅此而已。至于这个数据,究竟是用来干什么的,咱们其实能够并不用关心。

如何使用函数式编程

上面说的都是一些思惟上的东西,可能很稚嫩,但愿各位大大们能指出其中的错误,切不可吝啬言语。下面就来讲说函数式编程的一些具体的东西。

一等公民

所谓一等公民,说的是函数自己能够成为代码构成中的任意部分。具体的来讲,函数能够具备如下的特色:

  • 函数能够存储为变量

  • 函数能够成为数组的一个元素

  • 函数能够成为对象的成员变量

  • 函数能够在使用的时被直接建立

1 + (function(){ return 1 })(); //2
  • 函数能够被做为实参传递

  • 函数能够被另外一个函数返回

  • 函数能够返回另外一个函数

  • 函数能够做为形参

相信你们一看就懂了。

纯函数 (Pure Function)

在函数式编程中,有一个很重要的概念是纯函数。所谓纯函数就是

纯函数(Pure Function)与外界交换数据只有惟一渠道——参数和返回值。其在逻辑上没有反作用

可预见性

简单的说,就是你输入什么,就输出什么。输入和输出是可预见的。好比说像酱:

function add(x,y){
        x = _.isNumber(x)? x : 0;
        y = _.isNumber(y)? y : 0;
        return x+y;
    }
    
    add(1,2); // 3

这样的一个函数,你输入两个变量,你能够很肯定的,你获得的必定是二者之和。与之相异的,在javascript编程中很容易出现的,好比说酱:

var x = 10;
    function add10(y){
        return y+x;
    }
    
    add10(1); // 11
    
    x = 11;
    
    add10(1); //12

对于这个函数而言,函数自己是不可控的,若是外部的x发生改变,函数的返回值也随之会发生改变。那么若是想避免,应该怎么写呢:

function add(x){
        return function(y){
            return x+y;
        }
    }
    
    var add10 = add(10);
    
    add10(1); //11

这个时候,将函数所需的变量闭包在函数体的内部,函数的运算是不依赖于外界的变量的,你输入什么,就必定会输出什么。

完整性

为了实现函数的可控性,要保证,函数本省是完整的。函数的完整表如今,函数的运行不依赖于外界的环境变量。同时,函数的逻辑是完整的。好比说,酱:

<!DOCTYPE html>
    <html>
        <head>
            <title>a demo</title>
            <link herf="path/to/style" rel="stylesheet" />
            <script src="path/to/jq"></script>
        </head>
        <body>
            <div class=“container”>
                <span id="display"></span>
                <button id="getJson">获取数据</button>
            </div>
            <script type="appliaction/javascript">
                ;(function(){
                    $('#getJson').addEventListener('click',function(){
                        $.get('path/to/json',function(json){
                            $('#display').text(json);
                        })
                    },false)
                })()
            </script>
        </body>
    </html>

上面是咱们常常写的方式,固然啦,若是框架复杂一点,可能会多一点回调嵌套。可是,逻辑不出于此。可是呢,若是要函数完整,应该酱,额,我就写重要的部分啦:

var getJson = function(url,params){
        return $.getJson(url,params);
    }
    
    var display = function(text){
        $('#display').text(text)
    }
    
    var getJsonClickHandle = function(){
        getJson('url',{}).done(display)
    }
    
    var init = function(){
        $('#getJson').click(getJsonClickHandle);
    }
    
    
    init();

这时候,咱们抽象了整个行为。

点击 -> 获取数据 -> 显示数据。

酱,咱们把每一个行为转换成了一个单独的函数行为。这样的,每个函数都是单独的行为,能够很好的扩展和复制到其余地方。

同时,咱们也引出了一个纯函数很重要的部分。

可测试

咱们发现,当函数功能变的单一的时候,咱们能够很清晰的知道函数输入什么,输出什么的时候,咱们发现,这个代码的可测试性,获得了很大的提升。仍是用上面的两段代码,前者,根本不知道怎么去写测试,或者说,就是错了,你也不知道哪里错的,由于,全部的逻辑被各类匿名函数包裹了,很难很快的定位到问题的所在,后者,则容易了不少。

可组合(compose)

当函数纯化以后,有一个很鲜明的特色是,这个函数变的能够组合了。咱们能够像堆乐高积木同样,把各个咱们要用的函数堆起来变成一个更大得函数体。好比说酱:

使用了underscore.js;

function checker(){
        var validators = _.toArray(arguments);
        return function(obj){
            return _.reducer(validators,function(err,check){
                if(check(obj)){
                    return errs;
                }else{
                    return _.chain(errs).push(check.message).value();
                }
            },[])
        }
    }
    
    function validator(message,fun){
        var f = function(){
            return fun.apply(fun,arguments);
        };
        f['message'] = message;
        return f;
    }
    
    function hasKeys(){
        var KEYS = _.toArray(arguments);
        
        var fun = function(obj){
            return _.every(KEYS,function(key){
                return _.has(obj,key);
            });
        };
        
        fun.message = KEYS.concat([",this key is valid"]).join(" ");
        return fun;
    }
    
    var checkCommand = checker(hasKeys('msg','type'));
    
    checkCommand({}); // msg type , this key is valid

checkCommand就是咱们最后组合出来的能够进行校验的大城堡了,并且这个城堡能够定制化哦,甚至必要的时候,能够动态定制化。

高阶函数(high-order function)

高阶函数是函数式编程中,很重要的部分,咱们先来看看它是怎么定义的。做为一个高阶函数,他要知足如下定义:

  • 高阶函数必定是一等公民

  • 以一个函数为参数

  • 同时返回一个函数做为函数的返回值

举一个简单的例子:

var aFunc = (function(callback){
        return function(){
            callback&&callback();
        }
    })(function(){ console.log("I am a high-order function") });
    
    aFunc();// I am a high-order function;

额,呵呵,这个例子比较无聊哈,咱们看个更有意思的例子:

function calc(x){
        return function(y){
            return function(method){
                method&&method(x)(y);
            }
        }
    }
    function add(x){
        return function(y){
            console.log(x+y);
        }
    }
    calc(1)(2)(add);//3

固然,你再无聊点,非要写成这样,也不是不能够。

function calc(x){
        return function(method){
            return function(y){
                method&&method(x)(y);
            }
        }
    }
    calc(1)(add)(2);

其中的add方法是可自定义的。你能够把它换成任何一个你想要的函数。

柯理化函数(curry)

柯理化函数,是函数编程中很重要的一个方法。嗯,咱们先来看看定义:

只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

上文的add,calc都是柯理化的函数。
在平时的使用中,咱们常常如此使用之:

  • 接收一个函数

  • 返回一个只接收一个参数的函数

柯理化函数的定义是函数式编程的基础,咱们经过返回一个闭包的方式来使得函数参数变的能够捕捉,能够传递,能够保存。同时也使得,函数的行为变的能够分离,能够组合。

柯理方向

嗯,咱们知道运算符是有方向的,函数组合的大函数也是同样的。好比说,下面两个函数就不同:

var leftdiv(x){
        return function(y){
            return x/y;
        }
    }
    
    var rightdiv(x){
        return function(y){
            return y/x;
        }
    }

部分引用

咱们说到柯理化的函数能够保存参数,或者说成是保留运算场景。好比说咱们在上文举的add函数:

var add10 = add(10);
    add10(1);//11

其中的add10就是部分引用,add10这个函数保留了上一次函数调用时的运算场景,当下一个参数进来的时候,它可以继续运行,并给出结果。这样的好处是什么呢,咱们能够实现核心运算的前置条件校验。

好比说酱:

var add = function(x){
        if(!isNumber(x)) throw Error(' x must be a num');
        return function(y){
            if(!isNumber(y)) throw Error(' y must be a num');
            return function(){
                return x+y;
            }
        }
    }

咱们在每一次的调用的时候,咱们顺便作了输入参数的校验,当最后函数执行的时候,咱们能够确保,最后的函数执行是可靠的,也就是该函数是纯的。

组合

上文在说纯函数的时候,咱们就已经说到了组合了,这里,咱们再强调的地方是组合函数的管道特性。就是把上一个函数的值做为下一个函数的参数。
就像酱:

var compose = function(f,g){
        return function(x){
            return f(g(x));
        }
    }

基于流的编程

其实,对于函数式编程,咱们总结其技巧的时候,发现,其功能是围绕于:

  • 用函数传递函数

  • 用函数构造函数

  • 用函数调用函数

而这三个综合在一块儿,使得函数式编程可以实现基于数据流或者控制流。

链式编程

这个咱们都很熟悉啦,jquery就是这样干的,经过返回一个自身来实现链式调用。就像酱:

$.prototype.next(){
        //do something
        return this;
    }
    
    $('li').next().next();

promise

这个其实单独拿出来,写一本书。因此这里就不详细说了。例子的话,上文也有举getJson,这里就不举了。

链式编程和promise能更好的,让咱们按照数据处理的阶段去处理函数,在开始的进行参数校验,在加工的时候,进行数据的加工,在最后的时候,进行函数的显示。

总结

其实,这本翻来覆去的看了好几遍,一直想作一个总结,可是并不能作的出来。由于,咱们很容易发现,在实际的操做过程当中,咱们或多或少的都使用了函数式编程的一部分,咱们或多或少的都在践行函数式编程的理论,可是,若是说,咱们的代码就是使用函数式编程的时候,咱们又会发现,咱们的代码中,有很大一部分的逻辑,实在是没办法使用函数式编程进行处理。因此,后面有了响应式编程RXJs,经过订阅和发布模式来实现队列化的事件调度和资源分配,可是在实际使用过程当中,要想很快的将代码转化成函数式编程,须要对行为逻辑有很深入的理解和抽象,对步骤的分解,对函数的分解,对行为的分解,这个才是函数式编程中最难的部分,如何去思考你的数据发生了什么变化,你的状态发生了什么变化,去管理你的数据和你的状态。

相关文章
相关标签/搜索