盲猜一個:若是你有看過前端
這兩篇文章,你必定會對 JS 的【函數】有更多興趣!編程
若是你沒興趣?那我走?設計模式
皮一下,很舒服~ 沒錯!JS 就是輕量級的函數式編程!數組
拆解一下這句話,品味一下~markdown
本瓜將藉助《JavaScript 輕量級函數式編程》一書帶領你先透析它的落腳點函數式編程,而後再看看 JS 爲何被稱爲是 「輕量的」!閉包
此篇是《JS如何函數式編程?看這就夠了!》系列的第一篇,點贊👍關注👀持續追蹤!app
函數式編程(FP),不是一個新的概念,它幾乎貫穿了整個編程史。直到最近幾年,函數式編程才成爲整個開發界的主流觀念。編程語言
函數式編程有完善且清晰的原則,一旦咱們知道這些原則,咱們將能更加快速地讀懂代碼,定位問題。這是爲何函數式編程重要的緣由!
好比:你可能寫過一些命令式的代碼,像 if 語句和 for 循環這樣的語句。這些語句旨在精確地指導計算機如何完成一件事情。而聲明式代碼,以及咱們努力遵循函數式編程原則所寫出的代碼,更專一於描述最終的結果。
函數式編程以另外一種方式來思考代碼應該如何組織才能使數據流更加明顯,並能讓讀者很快理解你的思想。
記住,你編寫的每一行代碼以後都要有人來維護,這我的多是你的團隊成員,也多是將來的你。
函數式編程不是僅僅用 function 這個關鍵詞來編程,就像面向對象編程不只僅是用了對象就算是。
函數的真正意義是什麼?
回到最初的起點,咱們心中的函數必定是這樣的:
f(x) = 2x2 + 3
,這是數學上真正的函數。那這和函數式編程有什麼關係呢?
函數的本質是【映射】。以一個優雅的方式來描述一組值和另外一組值的映射關係,即函數的輸入值與輸出值之間的關聯關係。
在編程中,它或許有許多個輸入值,或許沒有。它或許有一個輸出值( return 值),或許沒有。
若是你計劃使用函數式編程,你應該儘量多地使用函數,而不是程序。你全部編寫的 function 應該接收輸入值,而且返回輸出值。(這麼作的緣由是多方面的,後續會一一介紹)
這裏,輸入值就是函數傳參,輸出值就是return
的東西。(若是你沒有 return 值,或者你使用 return;,那麼則會隱式地返回 undefined 值。)
function foo(x) {
if (x > 10) return x + 1;
var y = x / 2;
if (y > 3) {
if (x % 2 == 0) return x;
}
if (y > 1) return y;
return x;
}
複製代碼
foo(2) 返回什麼? foo(4) 返回什麼? foo(8), foo(12) 呢?
function foo(x) {
var retValue;
if (retValue == undefined && x > 10) {
retValue = x + 1;
}
var y = x / 2;
if (y > 3) {
if (retValue == undefined && x % 2 == 0) {
retValue = x;
}
}
if (retValue == undefined && y > 1) {
retValue = y;
}
if (retValue == undefined) {
retValue = x;
}
return retValue;
}
複製代碼
這樣寫會更容易理解嗎?
在每一個 retValue 能夠被設置的分支, 這裏都有個守護者以確保 retValue 沒有被設置過才執行。(?)
相比在函數中提前使用 return,咱們更應該用經常使用的流控制( if 邏輯 )來控制 retValue 的賦值。到最後,咱們 return retValue。
用 return 來實現流控制,會創造更多的隱含意義。
// 經過一個函數修改變量 y 的值
var y;
function foo(x) {
y = (2 * Math.pow( x, 2 )) + 3;
}
foo( 2 );
y;
複製代碼
可是咱們也能夠這樣寫:
function foo(x) {
return (2 * Math.pow( x, 2 )) + 3;
}
var y = foo( 2 );
y;
複製代碼
後一個版本更有優點嗎?
答案是確定的:有!
後一個版本中的 return 表示一個顯式輸出,而前者的 y 賦值是一個隱式輸出。
一般,開發人員喜歡顯式模式而不是隱式模式。
爲何說後者 return 出來的就是顯式的?而前者的 y 賦值是隱式的?
這個例子能夠給你答案:
function sum(list) {
var total = 0;
for (let i = 0; i < list.length; i++) {
if (!list[i]) list[i] = 0; // list 使用了 nums 的引用,不是對 [1,3,9,..] 的值複製,而是引用複製。
total = total + list[i];
}
return total;
}
var nums = [ 1, 3, 9, 27, , 84 ];
sum( nums ); // 124
複製代碼
這段代碼,除了 return 的輸出,還有沒有其它輸出可能改變到函數外部參數 nums 的值?
是有的!就是在註釋的一行,咱們無心中改變了 nums 。
console.log(nums) // [1, 3, 9, 27, 0, 84]
複製代碼
JS 對數組、對象和函數都使用引用和引用複製,咱們能夠很容易地從函數中建立輸出,即便是無意的。
這個隱式函數輸出在函數式編程中有一個特殊的名稱:反作用。
沒有反作用的函數也有一個特殊的名稱:純函數,這個概念十分重要,後面對有更多討論!
一個函數若是能夠接受或返回一個甚至多個函數,它被叫作高階函數。
其中最強大的就是:【閉包】。
咱們將在的後續舉例中大量使用閉包。它多是全部函數式編程中最重要的基礎。
此處舉一小例:
假設你須要將兩個值相加,一個你已經知道,另外一個還須要後面才能知道,你可使用閉包來記錄第一個輸入值:
function makeAdder(x) {
return function sum(y){
return x + y;
};
}
//咱們已經分別知道做爲第一個輸入的 10 和 37
var addTo10 = makeAdder( 10 );
var addTo37 = makeAdder( 37 );
// 緊接着,咱們指定第二個參數
addTo10( 3 ); // 13
addTo10( 90 ); // 100
addTo37( 13 ); // 50
複製代碼
這種在連續函數調用中指定輸入,是函數式編程中很是廣泛的形式。
它可分爲兩類:偏函數應用和柯里化。後續會展開。
咱們提倡要用具名函數,而不是匿名函數,這更有利於咱們語義化代碼,好比getPreferredName(..)
,操做意圖很明確,而且能夠很好的回溯問題,防止出現 (anonymous function)
。
可是 => 箭頭函數除外,箭頭函數仍是得有效利用。
=> 箭頭函數使人興奮的地方在於它幾乎徹底遵循函數的數學符號,特別是像 Haskell 這樣的函數式編程語言。它能簡化、優化代碼片斷中的空間。
JavaScript 中的 this 綁定規則是真的難記,好消息是咱們將把 this 丟棄掉,不去理會它。
這樣作的內核緣由是:this 是函數的一個隱式的輸入參數。前面咱們提到一般,開發人員喜歡顯式模式而不是隱式模式。,這樣的隱式輸入違背了咱們的原則。
函數是強大的!
咱們學習函數式編程的所有理由是爲了書寫更具可讀性的代碼。
程序中,函數不只僅是一個語句或者操做的集合,而是須要一個或多個輸入(理想狀況下只需一個!)和一個輸出。開發人員喜歡顯式輸入輸出而不是隱式輸入輸出。
函數內部的函數能夠取到閉包外部變量,並記住它們以備往後使用。這是全部程序設計中最重要的概念之一,也是函數式編程的基礎。
要警戒匿名函數,特別是 => 箭頭函數。雖然在編程時用起來很方便,可是會對增長代碼閱讀的負擔。
別用 this 敏感的函數。這不須要理由。
我是掘金安東尼,公衆號【掘金安東尼】,關注前端,也關注生活,持續輸出ing......