30分鐘入門JavaScript函數(shù)式編程

常用的編程思想有一下幾類:
1袭灯、面向過程編程刺下,最初級的,想到哪寫到哪稽荧;
2橘茉、面向?qū)ο缶幊蹋允挛餅橹行牡木幊趟枷胍陶桑压灿械膶傩院头椒ǚ庋b到一個類里畅卓;
3、面向切面編程构挤,統(tǒng)計一個函數(shù)執(zhí)行的時間髓介;
4惕鼓、函數(shù)式編程筋现,提純無關于業(yè)務的純函數(shù),函數(shù)嵌套讓函數(shù)更強大。(react中大量使用)

一矾飞、函數(shù)式編程思維及核心概念

1一膨、函數(shù)作為“一等公民”,可以賦值給其他變量洒沦,也可以作為參數(shù)豹绪,傳入另一個函數(shù),或者作為別的函數(shù)的返回值申眼。
2瞒津、map和reduce是最常見的函數(shù)式編程方法
var arr = [11, 22, 33];
arr.map(function(value, index, array){
    console.log(value, index, array);
})
//11  0  [11, 22, 33]
//22  1  [11, 22, 33]
//33  2  [11, 22, 33]

這里面.map本身就是一個函數(shù)

3、對于函數(shù)式編程括尸,里面的純函數(shù)巷蚪,對于同樣的輸入,一定會有同樣的輸出濒翻,永遠不依賴于外部狀態(tài)屁柏。
var xs = [1,2,3,4,5];
var result1 = xs.slice(0, 3);
console.log('xs:', xs);
console.log('result1:', result1);
// xs:  [1, 2, 3, 4, 5]
//result1:  [1, 2, 3]

var result2 = xs.splice(0, 3);
console.log('xs', xs);
console.log('result2', result2);
// xs:  [4, 5]
//result2:  [1, 2, 3]

可以發(fā)現(xiàn)Array.slice是一個純函數(shù),沒有副作用有送,對于固定的輸入總有固定的輸出淌喻。

純函數(shù)可以記憶(同樣的輸入總有同樣的輸出),不和外界有任何關系雀摘,抽象代碼方便單元測試

4裸删、函數(shù)的柯里化函數(shù),函數(shù)接受一堆參數(shù)阵赠,返回一個新函數(shù)烁落,繼續(xù)接收參數(shù)能夠處理邏輯。

柯里化之前:

var addFoo = (x, y) => x+y;
addFoo(1, 2); //3

柯里化之后:

var addFoo = x => ( y => x+y);
addFoo(1)(2);   //3

此處只是對柯里化概念的解釋豌注,其實一般簡單的處理是沒必要柯里化的伤塌。

柯里化是一種“預加載”函數(shù)的方法,通過傳遞較少的參數(shù)轧铁,得到一個已經(jīng)記住了這些參數(shù)的新函數(shù)每聪,某種意義上講,這是一種對參數(shù)的“緩存”齿风,是一種高效的編寫函數(shù)的方法药薯。

5、函數(shù)組合

純函數(shù)以及如何把它柯里化寫出洋蔥代碼h(g(f(x))),為了解決柯里化函數(shù)所最后生成的洋蔥樣的代碼救斑,需要用到“函數(shù)組合”
用函數(shù)柯里化改寫下面函數(shù)童本,讓多個函數(shù)像拼積木一樣:

const compose = (f, g) => (x => f(g(x)));
var first = arr => arr[0];
var reverse = arr => arr.reverse();
var last = compose(firtst, reverse);
last([1, 2, 3, 4, 5]);  //5

其實這里compose函數(shù)就是first函數(shù)和reverse函數(shù)組合在一起拼接成的一個組合函數(shù),先求數(shù)組的逆序函數(shù)脸候,再得出數(shù)組的第一個值穷娱。從最里層一層一層往外層剝開绑蔫。
函數(shù)組合相當于把一頁一頁的洋蔥貼在一起。

compose(f, compose(g泵额,h))
compose(compose(f, g), h)
compose(f, g, h)
6配深、Point Free

把一些對象自帶的方法轉(zhuǎn)化成純函數(shù),不要命名轉(zhuǎn)瞬即逝的中間變量嫁盲。
const f = str => str.toUpperCase().split(' ');
這個函數(shù)中篓叶,使用了str作為中間變量,但這個中間變量除了讓代碼變得長一點以外羞秤,毫無意義缸托。

var toUpperCase = word.toUpperCase();
var split = x =>(str => str.split(x));

var compose = (f, g) => (x => f(g(x)));
var f = compose(split(' '), toUpperCase);

f("abc  def");   //ABCDEF

這種風格能夠幫助我們減少不必要的命名,讓代碼保持簡潔和通用瘾蛋。

7嗦董、惰性函數(shù)

惰性函數(shù),只在第一次執(zhí)行,第一次執(zhí)行后再調(diào)用得到的結果都是一樣的瘦黑。對于瀏覽器來說京革,節(jié)省了時間和資源。
一個簡單的例子如下:

function test(a) {
    if (a == 1) {
        test = function () {
            return "ok"
        }
        return "ok";
    }else{
        test = function (argument) {
            return "no";
        }
        return "no";
    }
}
test(1)  // "ok"
test(10)   //"ok"

當?shù)谝淮螆?zhí)行test(1)后幸斥,函數(shù)的返回值固定返回“ok”了匹摇,所以不管傳的參數(shù)是多少,都是返回“ok”甲葬。
最典型的一個應用就是ajax中:

function createXHR(){
     var xhr=null;
     if(typeof XMLHttpRequest!='undefined'){
          xhr=new XMLHttpRequest();
         createXHR=function(){
               return XMLHttpRequest();  //直接返回一個懶函數(shù)廊勃,這樣不必在往下走
          }
      }else{
          try{
               xhr=new ActiveXObject("Msxml2.XMLHTTP");
              createXHR=function(){
                    return new ActiveXObject("Msxml2.XMLHTTP");
               }
          }catche(e){
               try{
                    xhr =new ActiveXObject("Microsoft.XMLHTTP");
                    createXHR=function(){
                         return new ActiveXObject("Microsoft.XMLHTTP");
                    }
               }catch(e){
                    createXHR=function(){
                         return null
                    }
               }        
         }
     }
}
8、高階函數(shù)

函數(shù)當參數(shù)经窖,把傳入的函數(shù)做一個封裝坡垫,然后返回這個封裝的函數(shù),達到更高程度的抽象画侣。

var add = function(a, b){
      return a+b;
}
function math(func, arr){
      return func(arr[0], arr[1]);
}

math(add, [12, 21, 31]);   //33
9冰悠、尾遞歸
function sum(n){
      if (n === 1){
            return 1;
      }
      return n + sum(n-1);
}

sum(4)
求值過程如下:
sum(4)
(4 + sum(3))
(4 + (3 + sum(2)))
(4 + (3 + (2 + sum(1))))
(4 + (3 + (2 + 1)))
(4 + (3 + 3))
(4 + 6)
10

普通遞歸時,內(nèi)存需要記錄調(diào)用的堆棧所處的深度和位置信息配乱,在最底層計算返回值溉卓,再根據(jù)記錄的信息,跳回上一層級計算搬泥,然后再跳回更高一層桑寨,依次運行,直到最外層的調(diào)用函數(shù)忿檩,cpu計算和內(nèi)存消耗很多尉尾,而且當深度過大時,會出現(xiàn)堆棧溢出燥透。

通過尾遞歸優(yōu)化后的代碼:

function sum(n, total){
      if (n === 1){
            return n + total;
      }
      return sum(n-1, n+total);
}

sum(5, 0)   //10
求值過程如下:
sum(5, 0)
sum(4, 5)
sum(3, 9)
sum(2, 12)
sum(1, 14)
1+14
15

整個過程是現(xiàn)行的沙咏,調(diào)用一次(x, total)后辨图,會進入下一個棧,相關數(shù)據(jù)信息會跟隨進入芭碍,不會放入堆棧保存。當計算完最后的值之后孽尽,直接返回到最上層的sum(5, 0)窖壕。這能有效的防止堆棧溢出。
尾部遞歸的的性能要高于傳統(tǒng)純函數(shù)的遞歸杉女。

10瞻讽、函數(shù)式編程中的閉包
function makePowerFn(power){
      function powerFn(base){
            return Math.pow(base, power); 
      }
      return powerFn;
}
var square = makePowerFn(2);
square(3);  //9

函數(shù)式編程其實是函數(shù)的種種技巧的拼接,但是函數(shù)式編程會充盈著大量的閉包熏挎,使用完需要釋放速勇,防止內(nèi)存泄漏。

二坎拐、比較流行的函數(shù)式編程庫

1烦磁、RxJS

RxJS(Reactive Extensions for JavaScript,JavaScript的響應式擴展)哼勇,其函數(shù)響應式編程理念非常先進都伪,雖然或許對于大部分應用環(huán)境來說,外部輸入時間并不是太頻繁积担,并不需要引入一個如此龐大的FRP(Functional Reactive Programming陨晶,函數(shù)響應式編程)體系,但我們也可以了解一下它的優(yōu)秀特性帝璧。

在RxJS中先誉,所有的外部輸入(用戶輸入、網(wǎng)絡請求等等)都被看做是一種“事件流”
用戶點擊了按鈕——>網(wǎng)絡請求成功——>用戶鍵盤輸入——>某個定時事件發(fā)生的烁,這種事件流特別適合處理游戲褐耳,上上下下,舉個簡單例子渴庆,下面這段代碼會監(jiān)聽點擊事件漱病,每2次點擊事件會產(chǎn)生一次事件響應:

var clicks = Rx.Observable
      .fromEvent(document, 'click')
      .bufferCount(2)
      .subscribe(x => console.log(x)); //打印出前2次點擊事件
2、Cycle.js

Cycle.js 是一個基于RxJS的框架把曼,它是一個徹底的FRP理念的框架杨帽,和React一樣支持virtual DOM,JSX語法嗤军,但現(xiàn)在似乎還沒有看到大型應用經(jīng)驗注盈。

本質(zhì)的講,它是在RxJS的基礎上加入了對virtual DOM叙赚,容器和組件的支持老客,比如下面就有一個簡單的“開關”按鈕:

function main(source){
      const sinks = {
            DOM: sources.DOM.select('input').events('click')
                  .map(ev => ev.target.checked)
                  .startWith(false)
                  .map(toggled => 
                  <div>
                        <input type="checkbox" />Toggle me
                        <p>{toggled ? 'ON' : 'OFF'}</p>
                  </div>
            )
      };
      return sinks;
}
const drivers = {
      DOM: makeDOMDriver('#app')
}

run(main, drivers);
3僚饭、Underscore.js

Underscore是一個JavaScript工具庫,它提供了一整套函數(shù)式編程的實用功能胧砰。但沒有擴展任何JavaScript內(nèi)置對象鳍鸵。它解決了這樣的問題——“如果我面對一個空白HTML頁面,并希望立即開始工作尉间,我需要什么偿乖?”,它彌補了jQuery沒有實現(xiàn)的功能哲嘲,同時又是backbone必不可少的部分贪薪。

underscore提供了100多個函數(shù),包括常用的map眠副、filter画切、invoke等等,還有一些輔助函數(shù)囱怕,如:函數(shù)綁定霍弹,JavaScript模板功能,創(chuàng)建快速索引娃弓,以及強類型相等測試等庞萍。

4、lodash.js

loadash是一個具有一致接口忘闻、模塊化钝计、高性能等特性的JavaScript工具庫,是underscore的fork齐佳,最初目的也是“一致的跨瀏覽器行為私恬,并改善性能”。

lodash采用延遲計算炼吴,意味著我們的鏈式方法在顯式或者隱式的value()調(diào)用之前是不會執(zhí)行的本鸣,因此lodash可以進行shortcut(捷徑)fusion(融合)這樣的優(yōu)化,通過合并鏈式大大降低迭代的次數(shù)硅蹦,從而大大提升其執(zhí)行性能荣德。

就如同jQuery在全部函數(shù)前加全局的”$“一樣,lodash使用全局的"_"來提供對工具的快速訪問童芹。

一個深層次查找屬性值的示例:

var _ = require('lodash');

// Fetch the name of the first pet from each owner
var ownerArr = [{
    "owner": "Colin",
    "pets": [{"name":"dog1"}, {"name": "dog2"}]
}, {
    "owner": "John",
    "pets": [{"name":"dog3"}, {"name": "dog4"}]
}];

// Array's map method.
ownerArr.map(function(owner){
   return owner.pets[0].name;
});

// Lodash
_.map(ownerArr, 'pets[0].name');

_.map 方法是對原生 map 方法的改進涮瞻,使用字符串處理深層次嵌套屬性的方式代替回調(diào)函數(shù)那些冗余的代碼。

5假褪、Ramda

Ramda是一個非常優(yōu)秀的工具庫署咽,跟同類比更函數(shù)式,主要體現(xiàn)在一下原則:

  • 1、Ramda里面提供的額函數(shù)全部是柯里化的意味著函數(shù)沒有默認參數(shù)可選宁否,從而減輕認知函數(shù)的難度窒升。(即:所有方法都支持柯里化,所有多參數(shù)的函數(shù)慕匠,默認都可以單參數(shù)使用饱须。)
    如下例子:
var R = require('ramda');

var square = n => n * n;

// 寫法一
R.map(square, [4, 8])

// 寫法二
R.map(square)([4, 8])
// 或者
var mapSquare = R.map(square);
mapSquare([4, 8]);

上面代碼中,寫法一是多參數(shù)版本台谊,寫法二是柯里化以后的單參數(shù)版本蓉媳。Ramda 都支持,并且推薦使用第二種寫法青伤。

  • 2督怜、Ramda推崇point free殴瘦,簡單的說就是使用簡單函數(shù)組合實現(xiàn)一個復雜功能狠角,而不是單獨寫一個函數(shù)操作臨時變量。

  • 3蚪腋、Ramda有個非常好的參數(shù)占位符R._ 丰歌,大大減輕了函數(shù)在point free過程中參數(shù)位置的問題。

和underscore屉凯、lodash比較立帖,Ramda要干凈很多。

三悠砚、函數(shù)式編程的實際應用場景

  • 易調(diào)試晓勇、熱部署、并發(fā)
  • 單元測試
1灌旧、易調(diào)試绑咱、熱部署、并發(fā)

① 函數(shù)式編程中的每個符號都是const的枢泰,于是沒有什么函數(shù)是有副作用的額描融。

② 函數(shù)式編程不需要考慮“死鎖”(deadlock),因為它不修改變量衡蚂,所以根本不存在“鎖”線程的問題窿克。

③ 函數(shù)式編程中的所有狀態(tài)都是傳給函數(shù)的參數(shù),而參數(shù)都是存儲在棧上的毛甲,這一特性年叮,讓軟件的熱部署變得十分簡單。只要比較一下正在運行的代碼和新的代碼獲得一個diff玻募,然后用這個diff更新現(xiàn)有的代碼谋右,新代碼的熱部署就完成了。

2补箍、單元測試

① 嚴格函數(shù)式編程的每一個符號都是對直接量或者表達式結果的引用改执,沒有函數(shù)產(chǎn)生副作用啸蜜。

②這是單元測試者的夢中仙境(wet dream),對被測試程序中的每個函數(shù)辈挂,你只需在意其參數(shù)衬横,而不必考慮函數(shù)調(diào)用順序,不用謹慎地設置外部狀態(tài)终蒂,所有要做的就是傳遞代表了邊際情況的參數(shù)蜂林。

四、總結

函數(shù)式編程不應該被視作靈丹妙藥拇泣,相反噪叙,它應該被視為我們現(xiàn)有工具的一個很自然的補充——它帶來了更高的可組合性,靈活性以及容錯性∶瓜瑁現(xiàn)代的JavaScript庫已經(jīng)開始嘗試擁抱函數(shù)式編程的概念以獲取這些優(yōu)勢睁蕾。比如,Redux作為一種Flux的變種實現(xiàn)债朵,核心理念也是狀態(tài)機和函數(shù)式編程子眶。

如果說面向?qū)ο缶幊探档蛷碗s度是靠良好的封裝、繼承序芦、多態(tài)以及接口的定義的話臭杰,那么函數(shù)式編程就是通過純函數(shù)以及他們的組合、柯里化谚中、Functor(函子)等技術來降低系統(tǒng)復雜度渴杆,而React、Rxjs宪塔、Cycle.js正是這種理念的代言磁奖。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蝌麸,隨后出現(xiàn)的幾起案子点寥,更是在濱河造成了極大的恐慌,老刑警劉巖来吩,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敢辩,死亡現(xiàn)場離奇詭異,居然都是意外死亡弟疆,警方通過查閱死者的電腦和手機戚长,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怠苔,“玉大人同廉,你說我怎么就攤上這事。” “怎么了迫肖?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵锅劝,是天一觀的道長。 經(jīng)常有香客問我蟆湖,道長故爵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任隅津,我火速辦了婚禮诬垂,結果婚禮上,老公的妹妹穿的比我還像新娘伦仍。我一直安慰自己结窘,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布充蓝。 她就那樣靜靜地躺著隧枫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪棺克。 梳的紋絲不亂的頭發(fā)上悠垛,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天线定,我揣著相機與錄音娜谊,去河邊找鬼。 笑死斤讥,一個胖子當著我的面吹牛纱皆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芭商,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼派草,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了铛楣?” 一聲冷哼從身側響起近迁,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎簸州,沒想到半個月后鉴竭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡岸浑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年搏存,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矢洲。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡璧眠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情责静,我是刑警寧澤袁滥,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站灾螃,受9級特大地震影響呻拌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜睦焕,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一藐握、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧垃喊,春花似錦猾普、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至乌助,卻和暖如春溜在,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背他托。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工掖肋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赏参。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓志笼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親把篓。 傳聞我的和親對象是個殘疾皇子纫溃,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

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