JavaScript函數(shù)編程中的一些值得學(xué)習(xí)的技術(shù)

純函數(shù)系谐, 高階函數(shù),函數(shù)組合盏混,函數(shù)柯里化蔚鸥,偏函數(shù)惜论,惰性載入函數(shù),緩存函數(shù)
這些概念在函數(shù)編程中真的是太常見了止喷,尤其是很多類庫實(shí)現(xiàn)或者組件封裝都會用到這些函數(shù)編程技巧馆类。

比如React-redux中的connect方法,React中的高階組件其實(shí)都或多或少用到了上述一些函數(shù)編程技巧弹谁。剛好最近有幸看到一篇關(guān)于這方面的文章乾巧,記錄一下。

在JS中预愤,函數(shù)總是被稱為一等公民沟于,那到底為什么會被稱為一等公民呢?主要是因?yàn)橹部担贘S中函數(shù)可以作為普通變量一樣使用旷太,可以作為函數(shù)的參數(shù),可以被賦值销睁,可以作為函數(shù)的return值供璧。這樣就導(dǎo)致了函數(shù)在JS中具有極其靈活的用法,因此也有了更加強(qiáng)大的功能冻记。

那下面就介紹下上面所說的這些內(nèi)容睡毒,在閱讀別人代碼或者自己封裝一些方法時都會很有用,最重要的是面試的時候被面試官問到了冗栗,不至于完全不知道而尷尷尬尬演顾。

1. 純函數(shù)
一個函數(shù)的返回結(jié)果只依賴于它的參數(shù),并且在執(zhí)行過程里面沒有副作用隅居,我們就把這個函數(shù)叫做純函數(shù)

重點(diǎn)有兩點(diǎn):一是返回結(jié)果只依賴它的參數(shù)钠至,二是執(zhí)行過程中沒有副作用。

執(zhí)行過程沒有副作用是指军浆,不會對函數(shù)外面的變量造成任何影響棕洋。

let num = 0;

// bar不是純函數(shù),因?yàn)榉祷刂狄蕾嚵送獠孔兞縩um
function bar(a, b) {
    return a+b+num;
}

// laa不是純函數(shù)乒融,執(zhí)行過程中產(chǎn)生副作用,影響外面num變量
function laa(a, b) {
    num++;
    return a+b;
}

// foo是純函數(shù)
function foo(a, b) {
    return a+b;
}

看一下上面的例子就ok了摄悯。使用純函數(shù)的原因是因?yàn)樗奶攸c(diǎn)是比較靠譜赞季,接收相同的參數(shù),就一定能輸出相同的值奢驯,這樣的程序易于調(diào)試申钩,不易出現(xiàn)莫名其妙的問題。

2. 高階函數(shù)
高階函數(shù)有兩種形式:其一是函數(shù)的參數(shù)是另一個函數(shù)(回調(diào)函數(shù))其二是函數(shù)的返回值是一個函數(shù)瘪阁。

函數(shù)參數(shù)為一個函數(shù)撒遣,可以理解成回調(diào)函數(shù)就是高階函數(shù)的一種邮偎,這樣理解我覺得沒有什么錯∫謇瑁可以參考知乎回調(diào)函數(shù)和高階函數(shù)的區(qū)別禾进?
提問下的回答。

ES6版本常見的數(shù)組的方法們map filter reduce等等都是高階函數(shù)廉涕。

函數(shù)的返回值是一個函數(shù)泻云,舉個例子,比如防抖(debouce)函數(shù)和節(jié)流(throttle)函數(shù)
借助下lodash的例子看下

// 避免窗口在變動時出現(xiàn)昂貴的計算開銷狐蜕。
jQuery(window).on('resize', _.debounce(calculateLayout, 150));
 
// 當(dāng)點(diǎn)擊時 `sendMail` 隨后就被調(diào)用宠纯。
jQuery(element).on('click', _.debounce(sendMail, 300, {
  'leading': true,
  'trailing': false
}));

接收一個或多個函數(shù)作為輸入,經(jīng)過加工最終輸出一個新的函數(shù)层释,中間可以定義一些其他邏輯婆瓜。比如再看個小例子:

function Eat(a,b){ //核心業(yè)務(wù)代碼
  console.log(a,b)
}

Function.prototype.before = function(callback){     //高階函數(shù)
  return (...args)=>{ //使用rest運(yùn)算符接收
    callback();
    this(...args);  //使用展開運(yùn)算符傳入
  }
}

let beforeEat = Eat.before(function(){ //自己擴(kuò)展業(yè)務(wù)代碼
  console.log("before eat")
})
beforeEat("米飯","牛肉") //傳參

可以用來擴(kuò)展函數(shù)功能。

3. 函數(shù)組合

函數(shù)組合就是將功能單一的函數(shù)贡羔,進(jìn)行組合返回一個功能更加強(qiáng)大的函數(shù)勃救。(如果看過鎧甲勇士的話,想象一下金木水火土合成帝皇俠那種感覺治力,如果沒看過就算了)

為了降低耦合性蒙秒,我們封裝的函數(shù)功能性比較單一,便于在不同的場景下使用宵统。比如如下函數(shù):

function lowerCase(str) {
   return str.toLowerCase();
}

function upperCase(str){
  return str.toUpperCase();
}

function trim(str) {
  return str.trim();
}

三個函數(shù)分別代表三個功能晕讲,當(dāng)有一天忽然需要轉(zhuǎn)換小寫和去除字符串的頭尾空格的函數(shù),比如lowerCaseAndtrim方法這時候可以實(shí)現(xiàn)一個compose方法如下調(diào)用马澈。

let lowerCaseAndtrim = compost(lowerCase, trim);
lowerCaseAndtrim('  JaVascRipt  '); 

最終目標(biāo)輸出 javascript;

接下來實(shí)現(xiàn)一下compose函數(shù)
思路:compose函數(shù)返回值是一個函數(shù)

function compose(...funcs) {
    return function(x) {
        let result = x;
        for(let i = 0; i < funcs.length; i++) {
            result = funcs[i](result)
        }
        return result;
    }
}

遍歷執(zhí)行compose方法接收的每一個方法瓢省,將目標(biāo)參數(shù)逐一交給每個函數(shù)執(zhí)行最終返回。

借助 Array.prototype.reduce實(shí)現(xiàn)

function compose(...funcs) {
    return (x) => {
        return funcs.reduce((prev, next) => {
            return next(prev);
        }, x)
    }
}

4. 函數(shù)柯里化
函數(shù)柯里化是一種可以實(shí)現(xiàn)函數(shù)多參變單參的方式痊班,在柯里化的過程總勤婚,將一個帶有多個參數(shù)的函數(shù),轉(zhuǎn)換為帶有一個參數(shù)的一系列的嵌套函數(shù)涤伐。它返回一個新函數(shù)馒胆,這個新函數(shù)期望傳入下一個參數(shù)。當(dāng)接收足夠的參數(shù)后凝果,會自動執(zhí)行原函數(shù)

function add(a, b, c) {
  return a+b+c;
}
let addCurry = curry(add) // curry就是函數(shù)實(shí)現(xiàn)柯里化的一個方法
addCurry(1)(2)(3); // 本來應(yīng)該add(1,2,3)調(diào)用的 進(jìn)行柯里化之后就可以addCurry(1)(2)(3) 調(diào)用了

關(guān)于函數(shù)柯里化的討論可以參考柯里化對函數(shù)式編程有何意義祝迂?

柯里化實(shí)現(xiàn):
思路:返回值是一個函數(shù),需要根據(jù)原函數(shù)參數(shù)是否等于調(diào)用時傳入的參數(shù)個數(shù)來判斷是否相等器净,相等直接傳入?yún)?shù)執(zhí)行原函數(shù)型雳,否則返回函數(shù),繼續(xù)接收參數(shù),并遞歸判斷原函數(shù)參數(shù)是否等于調(diào)用時傳入的參數(shù)個數(shù)邏輯纠俭。

function _curry(func) {
    return function curried(...args) {
        if(func.length === args.length) {
            return func.apply(this, args)
        } else {
            return function(...args2) {
                return curried.apply(this, args.concat(args2));
            }
        }
    }
}

5. 偏函數(shù)

偏函數(shù)指的是固定函數(shù)某些參數(shù)沿量,從而產(chǎn)生更小元的函數(shù)。元指的是函數(shù)的參數(shù)個數(shù)冤荆。
比如封裝函數(shù)的時候函數(shù)一共有三個參數(shù)朴则,第一個參數(shù)是固定不變的,后面的參數(shù)是變化的那么就可以使用偏函數(shù)匙赞。當(dāng)然也可以固定前兩個參數(shù)佛掖。

function add(int, a, b){
  return int+a+b;
}
let partialAdd = partial(add, 10);
partialAdd(1,2);  // 輸出13
partialAdd(3,4); // 輸出17

上面是偏函數(shù)使用形式,現(xiàn)在實(shí)現(xiàn)一個偏函數(shù)

function partial(func, ...args){
  return (...args2) => {
    return func.apply(this, [...args, ...args2]);
  }
}

6. 惰性載入函數(shù)
惰性載入函數(shù)旨在提升代碼性能涌庭。比如函數(shù)需要根據(jù)不同的判斷結(jié)果返回不同的值芥被,每一次執(zhí)行函數(shù)的時候就需要判斷一次,判斷的邏輯如果復(fù)雜的話坐榆,那么對于性能消耗就比較大拴魄。尤其是當(dāng)判斷條件是固定的時候使用惰性載入函數(shù)是非常適合的。

比如JS代碼判斷運(yùn)行平臺是安卓還是IOS

let isIos = 1;
function engin() {
    if(isIos){
        console.log('ios');
        // ios邏輯
    } else {
        console.log('android');
        // 安卓邏輯
    }
}
engin();
engin();

執(zhí)行這個engin方法的時候次都要去走判斷平臺邏輯席镀,性能肯定是消耗滴匹中,我們要的是當(dāng)?shù)谝淮螆?zhí)行的時候判斷下平臺邏輯,之后每一次執(zhí)行就不需要去判斷了豪诲,因?yàn)槠脚_是不變的顶捷,可以節(jié)省一些開銷。

惰性載入函數(shù)的實(shí)現(xiàn)1:利用命名覆蓋

 const isIos = 1;

let engin = function() {
    console.log('這里只執(zhí)行一次哦'); // 注意這里
    if(isIos){
        engin = function() {
            console.log('ios');
             // ios邏輯
        }
    } else {
        engin = function() {
            console.log('android');
            // 安卓邏輯
        }
    }
    return engin();
}
engin(); // ios
engin(); // ios
engin(); // ios

最終ios輸出3次屎篱,但是上面的console只輸出一次服赎。

惰性載入函數(shù)的實(shí)現(xiàn)2:利用立即執(zhí)行函數(shù)

const isIos = 1;
let engin = (function() {
    if(isIos){
        return function() {
            console.log('ios');
             // ios邏輯
        }
    } else {
        return function() {
            console.log('android');
            // 安卓邏輯
        }
    }
})();
engin();
engin();

JS文件加載時直接執(zhí)行一次,之后每次調(diào)用都是執(zhí)行判斷之后的邏輯交播。對于性能優(yōu)化還是比較有用的重虑。

7. 緩存函數(shù)
緩存函數(shù),指的是根據(jù)函數(shù)參數(shù)將函數(shù)執(zhí)行的結(jié)果緩存起來秦士,當(dāng)下次再調(diào)用的時候不用去計算就可以直接拿到結(jié)果缺厉,很明顯,這是一個空間換時間的做法隧土。
直接寫實(shí)現(xiàn)了提针。

function memorize(func) {
  let cache = Object.create(null);
  return (...args) => {
    let key = JSON.stringify(args);
    return cache[key] || (cache[key] = fn.apply(this, args));
  }
}
function add(a,b) {
    console.log('lala');
    return a+b;
}

let d = memorize(add);
console.log(d(1,2)); // 計算獲取
console.log(d(1,2)); // 從緩存獲取
console.log(d(1,3)); // 計算獲取
console.log(d(1,3)); // 從緩存獲取

當(dāng)?shù)诙我韵嗤瑓?shù)去計算的時候,默認(rèn)首先從緩存中找次洼,如果找到對應(yīng)的值关贵,那么直接返回,不參與計算卖毁。這樣程序運(yùn)行速度會比較快,但是,比較消耗內(nèi)存亥啦。

以上就是一些關(guān)于函數(shù)編程中的一些技巧使用炭剪。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翔脱,隨后出現(xiàn)的幾起案子奴拦,更是在濱河造成了極大的恐慌,老刑警劉巖届吁,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件错妖,死亡現(xiàn)場離奇詭異,居然都是意外死亡疚沐,警方通過查閱死者的電腦和手機(jī)暂氯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亮蛔,“玉大人痴施,你說我怎么就攤上這事【苛鳎” “怎么了辣吃?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長芬探。 經(jīng)常有香客問我神得,道長,這世上最難降的妖魔是什么偷仿? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任哩簿,我火速辦了婚禮,結(jié)果婚禮上炎疆,老公的妹妹穿的比我還像新娘卡骂。我一直安慰自己,他們只是感情好形入,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布全跨。 她就那樣靜靜地躺著,像睡著了一般亿遂。 火紅的嫁衣襯著肌膚如雪浓若。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天蛇数,我揣著相機(jī)與錄音挪钓,去河邊找鬼。 笑死耳舅,一個胖子當(dāng)著我的面吹牛碌上,可吹牛的內(nèi)容都是我干的倚评。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼馏予,長吁一口氣:“原來是場噩夢啊……” “哼天梧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起霞丧,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤呢岗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蛹尝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體后豫,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年突那,在試婚紗的時候發(fā)現(xiàn)自己被綠了挫酿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡陨收,死狀恐怖饭豹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情务漩,我是刑警寧澤拄衰,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站饵骨,受9級特大地震影響翘悉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜居触,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一妖混、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧轮洋,春花似錦制市、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至汉柒,卻和暖如春误褪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碾褂。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工兽间, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人正塌。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓嘀略,卻偏偏與公主長得像恤溶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子屎鳍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容