toxic-johann / toxic-johann.github.io

my blog
6 stars 0 forks source link

【2016-01-23】课堂总结——函数式编程菜鸟式试探 #14

Open toxic-johann opened 7 years ago

toxic-johann commented 7 years ago

为期两周的课程已经结束了,其中月影长达10小时的课程还是价值连城的。

月影在课堂上十分注重一个概念,就是函数式编程。那么什么是函数式编程呢?为什么需要函数式编程呢?函数式编程好不好呢?这些问题,每个人都有自己的答案,就像每个人都会有属于自己的设计模式,无需要强行去争论。

于是今天开始去研究这个问题。现在写下来,总结一下自己的理解,希望大家能够指正。

为什么我的代码出错了

JavaScript有一个十分著名的的概念,叫做回调地狱。就是形容一个函数中嵌入了太多的回调函数,令到函数十分难以理解。

于是大家提出了好多种方法去解决这个问题,并且也诞生了诸如promise这种优秀的策略。但是为什么会有这种策略呢,这种策略的优秀点何在呢?他用了什么方法呢。

我们先看一个面试题。

    var User={
        count:1,
        getCount:function(){return this.count}
    }

    console.log(User.getCount());//1
    var func = User.getCount;
    console.log(func());//undefined

这种原因是因为,func=User.getCount只是简单的移交了函数,而func执行的时候,this指向的是window,即console.log所在的对象。

那么这种问题怎么解决呢?有好几种方式的,第一就是如上面第一个做法那样,不要把名字换了,调用整个函数。

但是如果函数之前的对象十分长呢?我们这样运行起来十分不方便,我就是想让代码短一点呢。

这种情况下,我们可以用bind

    var func =  User.getCount.bind(User);

又或者在一些老一点的浏览器上,没有bind,我们可以这么写。

Function.prototype.bind = Function.protyotype.bind || function(context){
    var self=this
    return function(){
        return self.apply(context,arguments);
    };
}

我们就可以创造一个bind了。然后我们可以看到这个函数十分特别,因为他返回了一个函数。我们先记下这个特点。

这个时候我们就会想,我每次都要自己去bind这不很麻烦,可不可以在函数体内自身就bind好,我调用的时候不用想这些问题。

这个当然也是可以的啦。我们用apply来举个例子。

var User={
    count:1,
    getCount:function(){
        return function(){
            return this.count;
        }.apply(User,arguments);
    }
}
var func = User.getCount;
console.log(func());//1

我们可以看到,getCount也是返回了一个函数。所以返回函数的优点在哪里?

首先,我们可以看到,我们可以通过这种方式指定上下文,让我们在使用的时候无需担心this的问题。

我们的一些坏思想

假设我们业务线上有这么个需求,我们需要增加一个相加的函数,于是我们很快就写了出来了。

function add(x,y){
    return x+y;
}

某一天我们增加了需求,要累加。于是我们这么写。

function addMore(){
    var args = [].slice.call(arguments);
    var tmp = 0;
    for(var i = 0;i<args.length;i++){
        tmp = tmp + args[i];
    }
    return tmp;
}

我们发现,我们并没有复用add。这让我们做了些多余的事情。于是我们将它复用。

function addMore2(){
    var args = [].slice.call(arguments);
    var tmp = 0;
    for(var i = 0;i<args.length;i++){
        tmp = add(tmp,args[i]);
    }
    return tmp;
}

有一天我们发现,我们要做一个累乘,我们发现累乘和累加原则上是相同的,但是我们没有办法复用累加这种模式。所以,我们要曾加一种累*的方法。这很明显,就是要求我们为创造一个服务于函数的函数。我们发现,当业务放大到一定程度的时候,我们需要复用的是一套模式,一套方法。所以我们需要将方法抽取出来。我们可以这么写。

function add(x,y){
    return x+y;
}
function time(x,y){
    return x*y;
}
function concat(x,y){
    return x.toString()+y.toString();
}
function myReduce(func){
    return function(){
        var args = [].slice.call(arguments);
        var tmp=args[0];
        for(var i=1;i<args.length;i++){
            tmp = func(tmp,args[i]);
        }
        return tmp;
    }
}
var addMore = myReduce(add);
var timeMore = myReduce(time);
var concatMore = myReduce(concat);
console.log(addMore(1,2,3,4));//10
console.log(timeMore(1,2,3,4));//24
console.log(concatMore(1,2,3,4));//1234

我们可以看到这就实现了复用,这个myReduce就是和ES5中十分有用的reduce函数十分相似。我们可以通过这种方式创造新函数,增加复用性。

就像月影在课堂上说的,相对于数据,方法永远少的多和固定的多。如果我们可以将他抽取出来并且进行使用,毫无疑问,我们可以省下十分多的体力。

让程序语言更加可读

这一段主要来自于对阮一峰老师的《函数式编程初探》的研究。

里面举了这么一个例子。

我们平常编写数学运算,我们这么写

(1+2)*3/4

在程序里,我们这么写

var a = 1 + 2;
var b = a * 3;
var c = b / 4;

如果我们设计了函数,我们这么写

function add(x,y){return x+y);
function time(x,y){return x*y);
function except(x,y){return x/y);
except(time(add(1,2),3),4);//2.25

其实这种时候已经比较接近自然语言了,我们只要再增加一下链式调用,就可以实现更加人性化的语言了。

var myMath = {
    answer:0,
    add:function(){
        var args = [].slice.call(arguments);
        if(args.length>1){
            this.answer = args[0]+args[1];
        } else {
            this.answer+=args[0]
        }
        return this;
    },
    time:function(){
        var args = [].slice.call(arguments);
        if(args.length>1){
            this.answer = args[0]*args[1];
        } else {
            this.answer*=args[0]
        }
        return this;
    },
    except:function(){
        var args = [].slice.call(arguments);
        if(args.length>1){
            this.answer = args[0]/args[1];
        } else {
            this.answer/=args[0]
        }
        return this;
    }
}

console.log(myMath.add(1,2).time(3).except(4).answer);//2.25

为了偷懒,我们再抽取一部分。

var myMath = {
    answer:0,
    myPromise:function(func){
        var self = this;
        return function(){
            return function(){
                self.answer =  func.apply(this,arguments);
                return self;
            }.apply(myMath,arguments);
        }
    },
    myThen:function(){
        var self=this;
        var args = [].slice.call(arguments);
        var func = args[0];
        args.splice(0,1);
        return function(){
            var args = [].slice.call(arguments);
            args.unshift(this.answer);
            func.apply(self,args);
            return self;
        }.apply(myMath,args);
    }
}

function add(){
    var args = [].slice.call(arguments);
    return args[0]+args[1];
}
function time(){
    var args = [].slice.call(arguments);
    return args[0]*args[1];
}
function except(){
    var args = [].slice.call(arguments);
    return args[0]/args[1];
}

ad = myMath.myPromise(add);
ti = myMath.myPromise(time);
ex = myMath.myPromise(except);

console.log(ad(0,1).myThen(ad,2).myThen(ti,3).myThen(ex,4).answer);//2.25

于是,我们就可以拿到一个更加贴近我们需要的运算函数了。

好吧,我的理解大概就是这样子,感觉还是挺好玩的。