JavaScript 函數(shù)式編程

正文

一退子、什么是函數(shù)式編程?

我的理解是函數(shù)式編程就是一種抽象程度很高的編程范式切端,純粹的函數(shù)式編程語言編寫的函數(shù)可以看成沒有變量抬探,因此,任意一個函數(shù)帆赢,只要輸入是確定的小压,輸出就是確定的。.函數(shù)式編程不是用函數(shù)來編程椰于,也不是傳統(tǒng)的面向過程編程怠益。主旨在于將復雜的函數(shù)符合成簡單的函數(shù)(計算理論,或者遞歸論瘾婿,或者拉姆達演算)蜻牢。運算過程盡量寫成一系列嵌套的函數(shù)調(diào)用。函數(shù)式編程我覺得更貼近于數(shù)學思想或者說計算偏陪。

二抢呆、函數(shù)式編程的特性

1.函數(shù)是"第一等公民"

所謂"第一等公民"(first class),指的是函數(shù)與其他數(shù)據(jù)類型一樣笛谦,處于平等地位抱虐,可以賦值給其他變量,也可以作為參數(shù)饥脑,傳入另一個函數(shù)恳邀,或者作為別的函數(shù)的返回值。
舉例來說灶轰,下面代碼中的print變量就是一個函數(shù)谣沸,可以作為另一個函數(shù)的參數(shù)。

var print = function(i){ console.log(i);};
  [1,2,3].forEach(print);
2.只用”表達式"笋颤,不用"語句"

"表達式"(expression)是一個單純的運算過程乳附,總是有返回值;"語句"(statement)是執(zhí)行某種操作伴澄,沒有返回值赋除。函數(shù)式編程要求,只使用表達式秉版,不使用語句贤重。也就是說茬祷,每一步都是單純的運算清焕,而且都有返回值。
原因是函數(shù)式編程的開發(fā)動機,一開始就是為了處理運算(computation)秸妥,不考慮系統(tǒng)的讀寫(I/O)滚停。"語句"屬于對系統(tǒng)的讀寫操作,所以就被排斥在外粥惧。
當然键畴,實際應用中,不做I/O是不可能的突雪。因此起惕,編程過程中,函數(shù)式編程只要求把I/O限制到最小咏删,不要有不必要的讀寫行為惹想,保持計算過程的單純性。

3.沒有"副作用"

所謂"副作用"(side effect)督函,指的是函數(shù)內(nèi)部與外部互動(最典型的情況嘀粱,就是修改全局變量的值),產(chǎn)生運算以外的其他結(jié)果辰狡。
函數(shù)式編程強調(diào)沒有"副作用"锋叨,意味著函數(shù)要保持獨立,所有功能就是返回一個新的值宛篇,沒有其他行為娃磺,尤其是不得修改外部變量的值。

4.不修改狀態(tài)

在函數(shù)式編程中叫倍,我們通常理解的變量在函數(shù)式編程中也被函數(shù)代替了:在函數(shù)式編程中變量僅僅代表某個表達式豌鸡。這里所說的’變量’是不能被修改的。所有的變量只能被賦一次初值段标。(下面會詳細介紹變量被函數(shù)代替)

5.引用透明性

函數(shù)程序通常還加強引用透明性涯冠,即如果提供同樣的輸入,那么函數(shù)總是返回同樣的結(jié)果逼庞。就是說蛇更,表達式的值不依賴于可以改變值的全局狀態(tài)。

三赛糟、函數(shù)式編程常用核心概念和技術(shù)

1.純函數(shù)

什么是純函數(shù)呢派任?

對于相同的輸入,永遠會得到相同的輸出璧南,而且沒有任何可觀察的副作用掌逛,也不依賴外部環(huán)境的狀態(tài)的函數(shù),叫做純函數(shù)司倚。
舉個栗子:

var xs = [1,2,3,4,5];// Array.slice是純函數(shù)豆混,因為它沒有副作用篓像,對于固定的輸入,輸出總是固定的
xs.slice(0,3);
xs.slice(0,3);
xs.splice(0,3);// Array.splice會對原array造成影響皿伺,所以不純
xs.splice(0,3);
2.函數(shù)柯里化(高級函數(shù))

高階函數(shù)员辩,就是把函數(shù)當參數(shù),把傳入的函數(shù)做一個封裝鸵鸥,然后返回這個封裝函數(shù),達到更高程度的抽象奠滑。
我的理解:傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個函數(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

柯里化可以理解為是一種“預加載”函數(shù)的方法宋税,通過傳遞較少的參數(shù),得到一個已經(jīng)記住了這些參數(shù)的新函數(shù)讼油,某種意義上講弃甥,這是一種對參數(shù)的“緩存”,是一種非常高效的編寫函數(shù)的方法汁讼。

3.函數(shù)組合

為了解決函數(shù)嵌套過深淆攻,洋蔥代碼:h(g(f(x))),我們需要用到“函數(shù)組合”嘿架,我們可以
來用柯里化來改他瓶珊,讓多個函數(shù)像拼積木一樣。

函數(shù)編程的函數(shù)組合:兩個純函數(shù)組合之后返回了一個新函數(shù),舉個例子:

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

const toUpperCase = function(x) {
return x.toUpperCase();
};

const exclaim = function(x) {
return x + "!";
};
 
const shout = compose( exclaim , toUpperCase );
 
console.log(shout("hello world")); //HELLO WORLD!

可以對他簡化一下:

const compose=(f,g)=>(x=>f(g(x)))
const toUpperCase=x=>x.toUpperCase()
const exclaim=x=>x+'!'
const shout=compose(exclaim,toUpperCase)
console.log(shout('hello world'));  //HELLO WORLD!

compose中的參數(shù)的順序是隨意的耸彪,這就類似于乘法中的交換律 xy=yx**,
所以函數(shù)式編程貼近于數(shù)學計算伞芹。

在這里插入圖片描述

4.遞歸與尾遞歸

指函數(shù)內(nèi)部的最后一個動作是函數(shù)調(diào)用。 該調(diào)用的返回值蝉娜, 直接返回給函數(shù)唱较。 函數(shù)調(diào)用自身, 稱為遞歸召川。 如果尾調(diào)用自身南缓, 就稱為尾遞歸荧呐。 遞歸需要保存大量的調(diào)用記錄汉形, 很容易發(fā)生棧溢出錯誤, 如果使用尾遞歸優(yōu)化倍阐, 將遞歸變?yōu)檠h(huán), 那么只需要保存一個調(diào)用記錄岔冀, 這樣就不會發(fā)生棧溢出錯誤了。通俗點說使套,尾遞歸最后一步需要調(diào)用自身,并且之后不能有其他額外操作童漩。
舉個例子:我的理解為滿足遞歸條件相當于捕魚時先撒網(wǎng),等達到遞歸邊界即捕到魚時侧馅,再返回自身及收網(wǎng);尾遞歸就為當捕到魚時魚已經(jīng)到你手里了罗晕,具體舉個例子:

// 不是尾遞歸赠堵,無法優(yōu)化
function factorial(n) {
    if (n === 1) return 1;
    return n * factorial(n - 1);
}   //這里的遞歸當?shù)竭_邊界條件時會把值返回給上一個調(diào)用它的函數(shù)
 
function factorial(n, total) {
    if (n === 1) return total;
    return factorial(n - 1, n * total);
} //ES6強制使用尾遞歸
// 這里的尾遞歸就是當?shù)降走吔鐥l件時直接將結(jié)果 return 出來

我們看一下遞歸和尾遞歸執(zhí)行過程:
遞歸:

function sum(n) {
    if (n === 1) return 1;
    return n + sum(n - 1);
}
sum(5)
(5 + sum(4))
(5 + (4 + sum(3)))
(5 + (4 + (3 + sum(2))))
(5 + (4 + (3 + (2 + sum(1)))))
(5 + (4 + (3 + (2 + 1))))
(5 + (4 + (3 + 3)))
(5 + (4 + 6))
(5 + 10)
15      //遞歸非常消耗內(nèi)存,因為需要同時保存很多的調(diào)用幀揍愁,這樣,就很容易發(fā)生“棧溢出”

尾遞歸:

function sum(x, total) {
    if (x === 1) {
        return x + total;
    }
    return sum(x - 1, x + total);
}
sum(5, 0)
sum(4, 5)
sum(3, 9)
sum(2, 12)
sum(1, 14)
15

整個計算過程是線性的,調(diào)用一次sum(x, total)后怯屉,會進入下一個棧,相關(guān)的數(shù)據(jù)信息和跟隨進入羡儿,不再放在堆棧上保存缅叠。當計算完最后的值之后,直接返回到最上層的sum(5,0)领曼。這能有效的防止堆棧溢出。 在ECMAScript 6单刁,我們將迎來尾遞歸優(yōu)化,通過尾遞歸優(yōu)化褥傍,javascript代碼在解釋成機器碼的時候,將會向while看起朋贬,也就是說,同時擁有數(shù)學表達能力和while的效能糠亩。

5.惰性計算(求值)

在惰性計算中,表達式不是在綁定到變量時立即計算垂寥,而是在求值程序需要產(chǎn)生表達式的值時進行計算。延遲的計算使你可以編寫可能潛在地生成無窮輸出的函數(shù)过椎。因為不會計算多于程序的其余部分所需要的值,所以不需要擔心由無窮計算所導致的 out-of-memory 錯誤灰嫉。簡單的來說就是:

按需索取,能不多做事股耽,絕不多做

這里有自己實現(xiàn)的簡單的惰性求值
用JavaScript寫一個惰性求值的最簡實現(xiàn)
惰性求值官方文檔
官方文檔

四物蝙、函數(shù)式編程總結(jié)

1.函數(shù)式編程中的每個符號都是 const 的,于是沒有什么函數(shù)會有副作用震嫉。誰也不能在運行時修改任何東西,也沒有函數(shù)可以修改在它的作用域之外修改什么值給其他函數(shù)繼續(xù)使用悴势。這意味著決定函數(shù)執(zhí)行結(jié)果的唯一因素就是它的返回值侥加,而影響其返回值的唯一因素就是它的參數(shù)。
2.函數(shù)式編程不需要考慮”死鎖"(deadlock)矗蕊,因為它不修改變量朋魔,所以根本不存在"鎖"線程的問題。不必擔心一個線程的數(shù)據(jù),被另一個線程修改镶奉,所以可以很放心地把工作分攤到多個線程,部署"并發(fā)編程"(concurrency)。
3.函數(shù)式編程中所有狀態(tài)就是傳給函數(shù)的參數(shù)亿蒸,而參數(shù)都是儲存在棧上的。這一特性讓軟件的熱部署變得十分簡單。只要比較一下正在運行的代碼以及新的代碼獲得一個diff灰蛙,然后用這個diff更新現(xiàn)有的代碼宣旱,新代碼的熱部署就完成了耗溜。

參考文獻

JavaScript函數(shù)式編程指南

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阿宅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子往湿,更是在濱河造成了極大的恐慌惨好,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡职抡,警方通過查閱死者的電腦和手機窑邦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人揍瑟,你說我怎么就攤上這事月培。” “怎么了纯续?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長逢唤。 經(jīng)常有香客問我只锭,道長,這世上最難降的妖魔是什么裹驰? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任躏敢,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己枪蘑,他們只是感情好岳颇,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布术羔。 她就那樣靜靜地躺著释移,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上误窖,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天愈魏,我揣著相機與錄音想际,去河邊找鬼。 笑死,一個胖子當著我的面吹牛彩扔,可吹牛的內(nèi)容都是我干的敦捧。 我是一名探鬼主播绪颖,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼晨继,長吁一口氣:“原來是場噩夢啊……” “哼紊扬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤象对,失蹤者是張志新(化名)和其女友劉穎勒魔,沒想到半個月后冠绢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孵户,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了班眯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖违崇,靈堂內(nèi)的尸體忽然破棺而出诊霹,到底是詐尸還是另有隱情,我是刑警寧澤呵恢,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布鲸湃,位于F島的核電站鲜屏,受9級特大地震影響惯殊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜陕习,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望响谓。 院中可真熱鬧损合,春花似錦、人聲如沸娘纷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赖晶。三九已至律适,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間遏插,已是汗流浹背捂贿。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留胳嘲,地道東北人厂僧。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像了牛,于是被迫代替她去往敵國和親颜屠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349