入門函數(shù)式編程

函數(shù)式編程可以理解為以函數(shù)作為主要載體的編程方式,用函數(shù)去拆解夹孔、抽象一般的表達式

在函數(shù)式編程中,函數(shù)就是一個管道(pipe)析孽。這頭進去一個值搭伤,那頭就會出來一個新的值,沒有其他作用

與命令式相比袜瞬,這樣做的好處在哪怜俐?主要有以下幾點:

  • 語義更加清晰

  • 可復用性更高

  • 可維護性更好

  • 作用域局限,副作用少

compose

如果一個值要經(jīng)過多個函數(shù)邓尤,才能變成另外一個值佑菩,就可以把所有中間步驟合并成一個函數(shù),這叫做"函數(shù)的合成"(compose)

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

上圖中裁赠,X和Y之間的變形關系是函數(shù)f殿漠,Y和Z之間的變形關系是函數(shù)g,那么X和Z之間的關系佩捞,就是g和f的合成函數(shù)g·f绞幌。

函數(shù)的合成還必須滿足結合律。

結合律
compose(f, compose(g, h))
// 等同于
compose(compose(f, g), h)
// 等同于
compose(f, g, h)

compose實現(xiàn)思路就是先把傳入的函數(shù)都緩存起來一忱,然后在傳入數(shù)據(jù)的時候莲蜘,再挨個的使用apply執(zhí)行函數(shù), 上一個函數(shù)的輸出數(shù)據(jù)帘营,作為下一個函數(shù)的輸入數(shù)據(jù)

compose遵循的是從右向左運行票渠,而不是由內(nèi)而外運行桩了。也就是說compose是從最后一個函數(shù)開始執(zhí)行

const compose = function() {
      const args = arguments;
      let start = args.length - 1;
      return function() {
          let i = start;
          const result = args[start].apply(this, arguments);
          while (i--) result = args[i].call(this, result);
          return result;
      };
  };

函數(shù)就像數(shù)據(jù)的管道(pipe)扰藕。那么,函數(shù)合成就是將這些管道連了起來冗荸,讓數(shù)據(jù)一口氣從多個管道中穿過禀梳。組合讓我們的代碼簡單而富有可讀性杜窄。

curry

f(x)和g(x)合成為f(g(x)),有一個隱藏的前提算途,就是f和g都只能接受一個參數(shù)塞耕。如果可以接受多個參數(shù),比如f(x, y)和g(a, b, c)嘴瓤,函數(shù)合成就非常麻煩扫外。

這時就需要函數(shù)柯里化了莉钙。所謂"柯里化",就是把一個多參數(shù)的函數(shù)筛谚,轉化為單參數(shù)函數(shù)磁玉。

// 柯里化之前
function add(x, y) {
  return x + y;
}

add(1, 2) // 3

// 柯里化之后
function addX(y) {
  return function (x) {
    return x + y;
  };
}

addX(2)(1) // 3

柯里化實現(xiàn)思路就是當傳入的參數(shù)個數(shù)沒有到達func函數(shù)要求的參數(shù)個數(shù)的時候一直返回柯里化函數(shù)。 只有參數(shù)滿足func函數(shù)的個數(shù)的時候才通過apply執(zhí)行func函數(shù)

//不考慮函數(shù)的個數(shù)刻获,簡單的實現(xiàn)
function curry(fn) {
    // 獲取第一個參數(shù)以后的參數(shù)蜀涨,也就是除了fn以后的參數(shù)
    const args = [].slice.call(arguments, 1);
    return function() {
        //將當前函數(shù)和后面的函數(shù)的參數(shù)加起來
        var newArgs = args.concat([].slice.call(arguments));
        //將所有函數(shù)參數(shù)加起來并傳入fn執(zhí)行
        //不管this,目的就是執(zhí)行fn
        return fn.apply(this, newArgs);
    };
};
//考慮到參數(shù)個數(shù)
function curry(func , thisArg){
      //將所有參數(shù)收到thisArg
      if ( !Array.isArray(thisArg) ) {
          thisArg = [];
      }
      return function(){
          let args = Array.prototype.slice.call(arguments);
          if ( (args.length+thisArg.length) < func.length ) {
              return curry(func , thisArg.concat(args));
          }
          return func.apply(this , thisArg.concat(args));
      };
  }

說個題外話蝎毡,bind就是利用柯里化實現(xiàn)的厚柳,只不過兩者目的不一樣,柯里化是收集所有除參數(shù)函數(shù)的其他參數(shù)沐兵,然后執(zhí)行參數(shù)函數(shù)别垮,而bind是將第一個參數(shù)設為this,然后將所有其他參數(shù)放到參數(shù)函數(shù)里面執(zhí)行扎谎,柯里化不考慮this的問題碳想,而bind需要考慮this的問題,下面是bind的粗略實現(xiàn)

Function.prototype.bind = Function.prototype.bind ||
function(context){
    var self = this
    var args = Array.prototype.slice.call(arguments, 1)
    return function(){
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        //綁定context毁靶,確定this
        return self.apply(context,finalArgs);
    }
}
function add(a, b) {
    return a + b;
}

var addCurry = curry(add, 1, 2);
addCurry() // 3
//或者
var addCurry = curry(add, 1);
addCurry(2) // 3
//或者
var addCurry = curry(add);
addCurry(1, 2) // 3

有了柯里化以后胧奔,我們就能做到,所有函數(shù)只接受一個參數(shù)预吆。

pure function(轉載)

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

函數(shù)的返回結果只依賴于它的參數(shù)

const a = 1
const foo = (b) => a + b
foo(2) // => 3

foo 函數(shù)不是一個純函數(shù)岩遗,因為它返回的結果依賴于外部變量 a,我們在不知道 a 的值的情況下凤瘦,并不能保證 foo(2) 的返回值是 3宿礁。雖然 foo 函數(shù)的代碼實現(xiàn)并沒有變化,傳入的參數(shù)也沒有變化蔬芥,但它的返回值卻是不可預料的梆靖,現(xiàn)在 foo(2) 是 3,可能過了一會就是 4 了坝茎,因為 a 可能發(fā)生了變化變成了 2涤姊。

const a = 1
const foo = (x, b) => x + b
foo(1, 2) // => 3

現(xiàn)在 foo 的返回結果只依賴于它的參數(shù) x 和 b,foo(1, 2) 永遠是 3嗤放。今天是 3,明天也是 3壁酬,在服務器跑是 3次酌,在客戶端跑也 3恨课,不管你外部發(fā)生了什么變化,foo(1, 2) 永遠是 3岳服。只要 foo 代碼不改變剂公,你傳入的參數(shù)是確定的,那么 foo(1, 2) 的值永遠是可預料的吊宋。

這就是純函數(shù)的第一個條件:一個函數(shù)的返回結果只依賴于它的參數(shù)纲辽。

函數(shù)執(zhí)行過程沒有副作用

一個函數(shù)執(zhí)行過程對產(chǎn)生了外部可觀察的變化那么就說這個函數(shù)是有副作用的。

我們修改一下 foo:

const a = 1
const foo = (obj, b) => {
  return obj.x + b
}
const counter = { x: 1 }
foo(counter, 2) // => 3
counter.x // => 1

我們把原來的 x 換成了 obj璃搜,我現(xiàn)在可以往里面?zhèn)饕粋€對象進行計算拖吼,計算的過程里面并不會對傳入的對象進行修改,計算前后的 counter 不會發(fā)生任何變化这吻,計算前是 1吊档,計算后也是 1,它現(xiàn)在是純的唾糯。但是我再稍微修改一下它:

const a = 1
const foo = (obj, b) => {
  obj.x = 2
  return obj.x + b
}
const counter = { x: 1 }
foo(counter, 2) // => 4
counter.x // => 2

現(xiàn)在情況發(fā)生了變化怠硼,我在 foo 內(nèi)部加了一句 obj.x = 2,計算前 counter.x 是 1移怯,但是計算以后 counter.x 是 2香璃。foo 函數(shù)的執(zhí)行對外部的 counter 產(chǎn)生了影響,它產(chǎn)生了副作用舟误,因為它修改了外部傳進來的對象葡秒,現(xiàn)在它是不純的。

但是你在函數(shù)內(nèi)部構建的變量脐帝,然后進行數(shù)據(jù)的修改不是副作用:

const foo = (b) => {
  const obj = { x: 1 }
  obj.x = 2
  return obj.x + b
}

雖然 foo 函數(shù)內(nèi)部修改了 obj同云,但是 obj 是內(nèi)部變量,外部程序根本觀察不到堵腹,修改 obj 并不會產(chǎn)生外部可觀察的變化炸站,這個函數(shù)是沒有副作用的,因此它是一個純函數(shù)疚顷。

除了修改外部的變量旱易,一個函數(shù)在執(zhí)行過程中還有很多方式產(chǎn)生外部可觀察的變化,比如說調(diào)用 DOM API 修改頁面腿堤,或者你發(fā)送了 Ajax 請求阀坏,還有調(diào)用 window.reload 刷新瀏覽器,甚至是 console.log 往控制臺打印數(shù)據(jù)也是副作用笆檀。

純函數(shù)很嚴格忌堂,也就是說你幾乎除了計算數(shù)據(jù)以外什么都不能干,計算的時候還不能依賴除了函數(shù)參數(shù)以外的數(shù)據(jù)酗洒。

為什么要煞費苦心地構建純函數(shù)士修?因為純函數(shù)非臣纤欤“靠譜”,執(zhí)行一個純函數(shù)你不用擔心它會干什么壞事棋嘲,它不會產(chǎn)生不可預料的行為酒唉,也不會對外部產(chǎn)生影響。不管何時何地沸移,你給它什么它就會乖乖地吐出什么痪伦。如果你的應用程序大多數(shù)函數(shù)都是由純函數(shù)組成,那么你的程序測試雹锣、調(diào)試起來會非常方便

參考鏈接:

函數(shù)式編程入門教程

純函數(shù)(Pure Function)簡介

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末网沾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子笆制,更是在濱河造成了極大的恐慌绅这,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,496評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件在辆,死亡現(xiàn)場離奇詭異证薇,居然都是意外死亡,警方通過查閱死者的電腦和手機匆篓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評論 3 385
  • 文/潘曉璐 我一進店門浑度,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鸦概,你說我怎么就攤上這事箩张。” “怎么了窗市?”我有些...
    開封第一講書人閱讀 157,091評論 0 348
  • 文/不壞的土叔 我叫張陵先慷,是天一觀的道長。 經(jīng)常有香客問我咨察,道長论熙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,458評論 1 283
  • 正文 為了忘掉前任摄狱,我火速辦了婚禮脓诡,結果婚禮上,老公的妹妹穿的比我還像新娘媒役。我一直安慰自己祝谚,他們只是感情好,可當我...
    茶點故事閱讀 65,542評論 6 385
  • 文/花漫 我一把揭開白布酣衷。 她就那樣靜靜地躺著交惯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上商玫,一...
    開封第一講書人閱讀 49,802評論 1 290
  • 那天箕憾,我揣著相機與錄音牡借,去河邊找鬼拳昌。 笑死,一個胖子當著我的面吹牛钠龙,可吹牛的內(nèi)容都是我干的炬藤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,945評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼碴里,長吁一口氣:“原來是場噩夢啊……” “哼沈矿!你這毒婦竟也來了?” 一聲冷哼從身側響起咬腋,我...
    開封第一講書人閱讀 37,709評論 0 266
  • 序言:老撾萬榮一對情侶失蹤羹膳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后根竿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陵像,經(jīng)...
    沈念sama閱讀 44,158評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,502評論 2 327
  • 正文 我和宋清朗相戀三年寇壳,在試婚紗的時候發(fā)現(xiàn)自己被綠了醒颖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,637評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡壳炎,死狀恐怖泞歉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情匿辩,我是刑警寧澤腰耙,帶...
    沈念sama閱讀 34,300評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站铲球,受9級特大地震影響挺庞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜睬辐,卻給世界環(huán)境...
    茶點故事閱讀 39,911評論 3 313
  • 文/蒙蒙 一挠阁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧溯饵,春花似錦侵俗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春寻歧,著一層夾襖步出監(jiān)牢的瞬間掌栅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評論 1 266
  • 我被黑心中介騙來泰國打工码泛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留猾封,地道東北人。 一個月前我還...
    沈念sama閱讀 46,344評論 2 360
  • 正文 我出身青樓噪珊,卻偏偏與公主長得像晌缘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子痢站,可洞房花燭夜當晚...
    茶點故事閱讀 43,500評論 2 348

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

  • 長久以來磷箕,面向對象在 JavaScript 編程范式中占據(jù)著主導地位。不過阵难,最近人們對函數(shù)式編程的興趣正在增長岳枷。函...
    神刀閱讀 459評論 0 0
  • 關于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML 最堅實的梁柱呜叫;分享空繁,是 CSS 里最閃耀的一瞥;...
    iKcamp閱讀 1,373評論 0 2
  • 原文鏈接:https://github.com/EasyKotlin 值就是函數(shù)怀偷,函數(shù)就是值家厌。所有函數(shù)都消費函數(shù),...
    JackChen1024閱讀 5,957評論 1 17
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理椎工,服務發(fā)現(xiàn)饭于,斷路器,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • 昨夜维蒙,在朋友圈轉了一個暖心的小視頻掰吕,招來久不聯(lián)系的同學發(fā)來消息,聊著突然提到“琴”颅痊,說她剛離婚殖熟,問我是否知曉...
    鲇魚200276閱讀 219評論 0 0