溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

函數(shù)式編程

發(fā)布時(shí)間:2020-07-31 12:57:03 來源:網(wǎng)絡(luò) 閱讀:335 作者:jjjssswww 欄目:網(wǎng)絡(luò)安全

和Lisp、Haskell不同,javascript并非函數(shù)式編程語言,但在javascript中可以操控對(duì)象一樣操控函數(shù),也就是說可以在javascript中應(yīng)用函數(shù)式編程技術(shù)。ES5中的數(shù)組方法(如map()和reduce())就可以非常適合用于函數(shù)式編程風(fēng)格。本文將詳細(xì)介紹函數(shù)式編程

 

函數(shù)處理數(shù)組

  假設(shè)有一個(gè)數(shù)組,數(shù)組元素都是數(shù)字,想要計(jì)算這些元素的平均值和標(biāo)準(zhǔn)差。若使用非函數(shù)式編程風(fēng)格的話,如下所示 

函數(shù)式編程

var data = [1,1,3,5,5];var total = 0;for(var i = 0 ; i < data.length; i++){
    total += data[i];
}var mean = total/data.length;
total = 0;for(var i = 0; i < data.length; i++){    var deviation = data[i] - mean;
    total += deviation * deviation;
}var stddev = Math.sqrt(total/(data.length-1));

函數(shù)式編程

  可以使用數(shù)組方法map()和reduce()實(shí)現(xiàn)同樣的計(jì)算,這種實(shí)現(xiàn)極其簡潔

函數(shù)式編程

var sum = function(x,y){    return x+y;
}var square = function(x){    return x*x;
}var data = [1,1,3,5,5];var mean = data.reduce(sum)/data.length;var deviations = data.map(function(x){    return x - mean;
});var stddev = Math.sqrt(deviations.map(square).reduce(sum)/(data.length-1));

函數(shù)式編程

  在ES3中,并不包含這些數(shù)組方法,需要自定義map()和reduce()函數(shù)

函數(shù)式編程

//對(duì)于每個(gè)數(shù)組元素調(diào)用函數(shù)f(),并返回一個(gè)結(jié)果數(shù)組//如果Array.prototype.map定義了的話,就使用這個(gè)方法var map = Array.prototype.map ? function(a,f){return a.map(f);}
          : function (a,f){                var results = [];                for(var i = 0,len=a.length; i < len; i++){                    if(i in a){
                        results[i] = f.call(null,a[i],i,a);
                    }
                }                return results;
            }

函數(shù)式編程

函數(shù)式編程

//使用函數(shù)f()和可選的初始值將數(shù)組a減到一個(gè)值//如果Array.prototype.reduce存在的話,就使用這個(gè)方法var reduce = Array.prototype.reduce 
            ? function(a,f,initial){                if(arguments.length > 2){                    return a.reduce(f,initial);                    
                }else{                    return a.reduce(f);
                }
            }
            : function(a,f,initial){                var i = 0, len = a.length ,accumulator;                if(argument.length > 2){
                    accumulator = initial;
                }else{                    if(len == 0){                        throw TypeError();
                    }                    while(i < len){                        if(i in a){
                            accumulator = a[i++];                            break;
                        }else{
                            i++;
                        }
                    }                    if(i == len){                        throw TypeError();
                    }
                }                while(i < len){                    if(i in a){
                        accumulator = f.call(undefined,accumulator,a[i],i,a);
                    }
                    i++;
                }                return accumulator;
            }

函數(shù)式編程

 

高階函數(shù)

  高階函數(shù)(higher-order function)指操作函數(shù)的函數(shù),它接收一個(gè)或多個(gè)函數(shù)作為參數(shù),并返回一個(gè)新函數(shù)

函數(shù)式編程

//這個(gè)高階函數(shù)返回一個(gè)新的函數(shù),這個(gè)新函數(shù)將它的實(shí)參傳入f(),并返回f的返回值的邏輯非function not(f){    return function(){        var result = f.apply(this,arguments);        return !result;
    };
}var even = function(x){    return x % 2 === 0;
}var odd = not(even);
[1,1,3,5,5].every(odd);//true

函數(shù)式編程

  上面的not()函數(shù)就是一個(gè)高階函數(shù),因?yàn)樗邮找粋€(gè)函數(shù)作為參數(shù),并返回一個(gè)新函數(shù)

  下面的mapper()函數(shù),也是接收一個(gè)函數(shù)作為參數(shù),并返回一個(gè)新函數(shù),這個(gè)新函數(shù)將一個(gè)數(shù)組映射到另一個(gè)使用這個(gè)函數(shù)的數(shù)組上

函數(shù)式編程

//所返回的函數(shù)的參數(shù)應(yīng)當(dāng)是一個(gè)實(shí)參數(shù)組,并對(duì)每個(gè)數(shù)組元素執(zhí)行函數(shù)f(),并返回所有計(jì)算結(jié)果組成的數(shù)組function mapper(f){    return function(a){        return map(a,f);
    }
}var increment = function(x){    return x+1;
}var incrementer = mapper(increment);
increment([1,2,3]);//[2,3,4]

函數(shù)式編程

  下面是一個(gè)更常見的例子,它接收兩個(gè)函數(shù)f()和g(),并返回一個(gè)新函數(shù)用以計(jì)算f(g())

函數(shù)式編程

//返回一個(gè)新的可以計(jì)算f(g(...))的函數(shù)//返回的函數(shù)h()將它所有的實(shí)參傳入g(),然后將g()的返回值傳入f()//調(diào)用f()和g()時(shí)的this值和調(diào)用h()時(shí)的this值是同一個(gè)thisfunction compose(f,g){    return function(){        //需要給f()傳入一個(gè)參數(shù),所以使用f()的call()方法
        //需要給g()傳入很多參數(shù),所以使用g()的apply()方法
        return f.call(this,g.apply(this,arguments));
    };
}var square = function(x){    return x*x;
}var sum = function(x,y){    return x + y;
}var squareofsum = compose(square,sum);
squareofsum(2,3);//25

函數(shù)式編程

 

不完全函數(shù)

  不完全函數(shù)是一種函數(shù)變換技巧,即把一次完整的函數(shù)調(diào)用拆成多次函數(shù)調(diào)用,每次傳入的實(shí)參都是完整實(shí)參的一部分,每個(gè)拆分開的函數(shù)叫做不完全函數(shù),每次函數(shù)調(diào)用叫做不完全調(diào)用。這種函數(shù)變換的特點(diǎn)是每次調(diào)用都返回一個(gè)函數(shù),直到得到最終運(yùn)行結(jié)果為止

  函數(shù)f()的bind()方法返回一個(gè)新函數(shù),給新函數(shù)傳入特定的上下文和一組指定的參數(shù),然后調(diào)用函數(shù)f()。bind()方法只是將實(shí)參放在完整實(shí)參列表的左側(cè),也就是說傳入bind()的實(shí)參都是放在傳入原始函數(shù)的實(shí)參列表開始的位置,但有時(shí)希望將傳入bind()的實(shí)參放在完整實(shí)參列表的右側(cè)

函數(shù)式編程

//實(shí)現(xiàn)一個(gè)工具函數(shù)將類數(shù)組對(duì)象(或?qū)ο?轉(zhuǎn)換為真正的數(shù)組function array(a,n){    return Array.prototype.slice.call(a,n||0);
}//這個(gè)函數(shù)的實(shí)參傳遞到左側(cè)function partialLeft(f){    var args = arguments;    return function(){        var a = array(args,1);
        a = a.concat(array(arguments));        return f.apply(this,a);
    };
}//這個(gè)函數(shù)的實(shí)參傳遞到右側(cè)function partialRight(f){    var args = arguments;    return function(){        var a = array(arguments);
        a = a.concat(array(args,1));        return f.apply(this,a);
    };
}//這個(gè)函數(shù)的實(shí)參被用作模板,實(shí)參列表中的undefined值都被填充function partial(f){    var args = arguments;    return function(){        var a = array(args,1);        var i = 0, j = 0;        //遍歷args,從內(nèi)部實(shí)參填充undefined值
        for(;i<a.length;i++){            if(a[i] === undefined){
                a[i] = arguments[j++];
            }            //現(xiàn)在將剩下的內(nèi)部實(shí)參都追加進(jìn)去        };
        a = a.concat(array(arguments,j));        return f.apply(this,a);
    }
}//這個(gè)函數(shù)有三個(gè)實(shí)參var f = function(x,y,z){    return x*(y - z);
}//注意這三個(gè)不完全調(diào)用之間的區(qū)別partialLeft(f,2)(3,4);//2*(3-4)=-2partialRight(f,2)(3,4);//3*(4-2)=6partial(f,undefined,2)(3,4);//3*(2-4)=-6

函數(shù)式編程

  利用這種不完全函數(shù)的編程技巧,可以編寫一些有意思的代碼,利用已有的函數(shù)來定義新的函數(shù)

var increment = partialLeft(sum,1);var cuberoot = partialRight(Math.pow,1/3);
String.prototype.first = partial(String.prototype.charAt,0);
String.prototype.last = partial(String.prototype.substr,-1,1);

  當(dāng)將不完全調(diào)用和其他高階函數(shù)整合在一起時(shí),事件就變得格外有趣了。比如,下例定義了not()函數(shù)

函數(shù)式編程

var not = partialLeft(compose,function(x){    return !x;
});var even = function(x){    return x % 2 === 0;
};var odd = not(even);var isNumber = not(isNaN);

函數(shù)式編程

  可以使用不完全調(diào)用的組合來重新組織求平均數(shù)和標(biāo)準(zhǔn)差的代碼,這種編碼風(fēng)格是非常純粹的函數(shù)式編程

函數(shù)式編程

var data = [1,1,3,5,5];var sum = function(x,y){return x+y;}var product = function(x,y){return x*y;}var neg = partial(product,-1);var square = partial(Math.pow,undefined,2);var sqrt = partial(Math.pow,undefined,.5);var reciprocal = partial(Math.pow,undefined,-1);var mean = product(reduce(data,sum),reciprocal(data.length));var stddev = sqrt(product(reduce(map(data,compose(square,partial(sum,neg(mean)))),sum),reciprocal(sum(data.length,-1))));

函數(shù)式編程

 

記憶

  將上次的計(jì)算結(jié)果緩存起來,在函數(shù)式編程中,這種緩存技巧叫做記憶(memorization)。記憶只是一種編程技巧,本質(zhì)上是犧牲算法的空間復(fù)雜度以換取更優(yōu)的時(shí)間復(fù)雜度,在客戶端javascript中代碼的執(zhí)行時(shí)間復(fù)雜度往往成為瓶頸,因此在大多數(shù)場景下,這種犧牲空間換取時(shí)間的做法以提升程序執(zhí)行效率的做法是非??扇〉?/p>

函數(shù)式編程

//返回f()的帶有記憶功能的版本//只有當(dāng)f()的實(shí)參的字符串表示都不相同時(shí)它才會(huì)工作function memorize(f){    var cache = {};//將值保存到閉包內(nèi)
    return function(){        //將實(shí)參轉(zhuǎn)換為字符串形式,并將其用做緩存的鍵
        var key = arguments.length + Array.prototype.join.call(arguments ,",");        if(key in cache){            return cache[key];
        }else{            return cache[key] = f.apply(this,arguments);
        }
    }
}

函數(shù)式編程

  memorize()函數(shù)創(chuàng)建一個(gè)新的對(duì)象,這個(gè)對(duì)象被當(dāng)作緩存的宿主,并賦值給一個(gè)局部變量,因此對(duì)于返回的函數(shù)來說它是私有的。所返回的函數(shù)將它的實(shí)參數(shù)組轉(zhuǎn)換成字符串,并將字符串用做緩存對(duì)象的屬性名。如果在緩存中存在這個(gè)值,則直接返回它;否則,就調(diào)用既定的函數(shù)對(duì)實(shí)參進(jìn)行計(jì)算,將計(jì)算結(jié)果緩存起來并返回

函數(shù)式編程

//返回兩個(gè)整數(shù)的最大公約數(shù)function gcd(a,b){    var t;    if(a < b){
        t = b, b = a, a = t; 
    }    while(b != 0){
        t = b, b = a % b, a = t;
    }    return a;
}var gcdmemo = memorize(gcd);
gcdmemo(85,187);//17

函數(shù)式編程

  寫一個(gè)遞歸函數(shù)時(shí),往往需要實(shí)現(xiàn)記憶功能,我們更希望調(diào)用實(shí)現(xiàn)了記憶功能的遞歸函數(shù),而不是原遞歸函數(shù)

var factorial = memorize(function(n){    return (n<=1) ? 1 : n*factorial(n-1);
});
factorial(5);//120

 

連續(xù)調(diào)用單參函數(shù)

  下面利用連續(xù)調(diào)用單參函數(shù)來實(shí)現(xiàn)一個(gè)簡易的加法運(yùn)算

add(num1)(num2)(num3)…; 
add(10)(10) = 20add(10)(20)(50) = 80add(10)(20)(50)(100) = 180

  如果完全按照上面實(shí)現(xiàn),則無法實(shí)現(xiàn),因?yàn)閍dd(1)(2)如果返回3,add(1)(2)(3)必然報(bào)錯(cuò)。于是,有以下兩種變形方法

  第一種變形如下:

add(num1)(num2)(num3)…; 
add(10)(10)() = 20add(10)(20)(50)() = 80add(10)(20)(50)(100)() = 180

函數(shù)式編程

function add(n){    return function f(m){  
        if(m === undefined){            return n;
        }else{
            n += m;            return f;
        }
    }
}
console.log(add(10)());//10console.log(add(10)(10)());//20console.log(add(10)(10)(10)());//30

函數(shù)式編程

  第二種變形如下:

add(num1)(num2)(num3)…; 
+add(10)(10) = 20
+add(10)(20)(50) = 80
+add(10)(20)(50)(100) = 180

函數(shù)式編程

function add(n) {    function f(m){
        n += m;        return f;
    };
    f.toString = f.valueOf = function () {return n}    return f;
}
console.log(+add(10));//10console.log(+add(10)(10));//20console.log(+add(10)(10)(10));//30



向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI