做者:Fernando Doglio
譯者:前端小智
來源:medium
移動端閱讀: 點這裏
點贊再看,微信搜索
【大遷世界】 關注這個沒有大廠背景,但有着一股向上積極心態人。本文
GitHub
https://github.com/qq44924588... 上已經收錄,文章的已分類,也整理了不少個人文檔,和教程資料。
你們都說簡歷沒項目寫,我就幫你們找了一個項目,還附贈【搭建教程】。前端
面向對象編程和函數式編程是兩種很是不一樣的編程範式,它們有本身的規則和優缺點。git
可是,JavaScript 並無一直遵循一個規則,而是正好處於這兩個規則的中間,它提供了普通OOP語言的一些方面,好比類、對象、繼承等等。但與此同時,它還爲你提供了函數編程的一些概念,好比高階函數以及組合它們的能力。github
咱們行人人三個概念中最重要的一個開始:高階函數。編程
高階函數意味着函數不只僅是一個能夠從代碼中定義和調用,實際上,你能夠將它們用做可分配的實體。若是你使用過一些JavaScript,那麼這並不奇怪。將匿名函數分配給常量,這樣的事情很是常見。數組
const adder = (a, b) => { return a + b }
上述邏輯在許多其餘語言中是無效的,可以像分配整數同樣分配函數是一個很是有用的工具,實際上,本文涵蓋的大多數主題都是該函數的副產品。微信
有了高階函數,咱們不只能夠像上面那樣分配函數,還能夠在函數調用時將它們做爲參數傳遞。這爲建立一常動態的代碼基打開了大門,在這個代碼基礎上,能夠直接將複雜行爲做爲參數傳遞來重用它。編程語言
想象一下,在純面向對象的環境中工做,你想擴展類的功能,以完成任務。 在這種狀況下,你可能會使用繼承,方法是將該實現邏輯封裝在一個抽象類中,而後將其擴展爲一組實現類。 這是一種完美的 OOP 行爲,而且行之有效,咱們:函數式編程
如今,咱們想要的是重用邏輯,咱們能夠簡單地將可重用邏輯提取到函數中,而後將該函數做爲參數傳遞給任何其餘函數,這種方法,能夠少省去一些建立「樣板」過程,由於,咱們只是在建立函數。函數
下面的代碼顯示瞭如何在 OOP 中重用程序邏輯。工具
//Encapsulated behavior封裝行爲stract class LogFormatter { format(msg) { return Date.now() + "::" + msg } } //重用行爲 class ConsoleLogger extends LogFormatter { log(msg) { console.log(this.format(msg)) } } class FileLogger extends LogFormatter { log(msg) { writeToFileSync(this.logFile, this.format(msg)) } }
第二個示是將邏輯提取到函數中,咱們能夠混合匹配輕鬆建立所需的內容。 你能夠繼續添加更多格式和編寫功能,而後只需將它們與一行代碼混合在一塊兒便可:
// 泛型行爲抽象 function format(msg) { return Date.now() + "::" + msg } function consoleWriter(msg) { console.log(msg) } function fileWriter(msg) { let logFile = "logfile.log" writeToFileSync(logFile, msg) } function logger(output, format) { return msg => { output(format(msg)) } } // 經過組合函數來使用它 const consoleLogger = logger(consoleWriter, format) const fileLogger = logger(fileWriter, format)
這兩種方法都有優勢,並且都很是有效,沒有誰最優。這裏只是展現這種方法的靈活性,咱們有能力經過 行爲(即函數)做爲參數,就好像它們是基本類型(如整數或字符串)同樣。
對於這個好處,一個很好的例子就是Array
方法,例如forEach
,map
,reduce
等等。 在非函數式編程語言(例如C)中,對數組元素進行迭代並對其進行轉換須要使用for
循環或某些其餘循環結構。 這就要求咱們以指定方式編寫代碼,就是需求描述循環發生的過程。
let myArray = [1,2,3,4] let transformedArray = [] for(let i = 0; i < myArray.length; i++) { transformedArray.push(myArray[i] * 2) }
上面的代碼主要作了:
i
,該變量將用做myArray
的索引,其值的範圍爲0
到myArray
的長度i
的每一個值,將myArray
的值在i
的位置相乘,並將其添加到transformedArray
數組中。這種方法頗有效,並且相對容易理解,然而,這種邏輯的複雜性會隨着項目的複雜程度上升而上升,認知負荷也會隨之增長。可是,像下面這種方式就更容易閱讀:
const double = x => x * 2; let myArray = [1,2,3,4]; let transformedArray = myArray.map(double);
與第一種方式相比,這種方式更容易閱讀,並且因爲邏輯隱藏在兩個函數(map
和double
)中,所以你沒必要擔憂瞭解它們的工做原理。 你也能夠在第一個示例中將乘法邏輯隱藏在函數內部,可是遍歷邏輯必須存在,這就增長了一些沒必要要的閱讀阻礙。
函數柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。咱們來看個例子:
function adder(a, b) { return a + b } // 變成 const add10 = x => adder(a, 10)
如今,若是你要作的就是將10
添加到一系列值中,則能夠調用add10
而不是每次都使用相同的第二個參數調用adder
。 這個事例看起來比較蠢,但它是體現了 柯里化
的理想。
你能夠將柯里化視爲函數式編程的繼承,而後按照這種思路再回到logger
的示例,能夠獲得如下內容:
function log(msg, msgPrefix, output) { output(msgPrefix + msg) } function consoleOutput(msg) { console.log(msg) } function fileOutput(msg) { let filename = "mylogs.log" writeFileSync(msg, filename) } const logger = msg => log(msg, ">>", consoleOutput); const fileLogger = msg => log(msg, "::", fileOutput);
log
的函數須要三個參數,而咱們將其引入僅須要一個參數的專用版本中,由於其餘兩個參數已由咱們選擇。
注意,這裏將log
函數視爲抽象類,只是由於在個人示例中,不想直接使用它,可是這樣作是沒有限制的,由於這只是一個普通的函數。 若是咱們使用的是類,則將沒法直接實例化它。
函數組合就是組合兩到多個函數來生成一個新函數的過程。將函數組合在一塊兒,就像將一連串管道扣合在一塊兒,讓數據流過同樣。
在計算機科學中,函數組合是將簡單函數組合成更復雜函數的一種行爲或機制。就像數學中一般的函數組成同樣, 每一個函數的結果做爲下一個函數的參數傳遞,而最後一個函數的結果是整個函數的結果。
這是來自維基百科的函數組合的定義,粗體部分是比較關鍵的部分。使用柯里化時,就沒有該限制,咱們能夠輕鬆使用預設的函數參數。
代碼重用聽起來很棒,可是實現起來很難。若是代碼業務性過於具體,就很難重用它。如時代碼太過通用簡單,又不多人使用。因此咱們須要平衡二者,一種製做更小的、可重用的部件的方法,咱們能夠將其做爲構建塊來構建更復雜的功能。
在函數式編程中,函數是咱們的構建塊。每一個函數都有各自的功能,而後咱們把須要的功能(函數)組合起來完成咱們的需求,這種方式有點像樂高的積木,在編程中咱們稱爲 組合函數。
看下如下兩個函數:
var add10 = function(value) { return value + 10; }; var mult5 = function(value) { return value * 5; };
上面寫法有點冗長了,咱們用箭頭函數改寫一下:
var add10 = value => value + 10; var mult5 = value => value * 5;
如今咱們須要有個函數將傳入的參數先加上 10 ,而後在乘以 5, 以下:
如今咱們須要有個函數將傳入的參數先加上 10 ,而後在乘以 5, 以下:
var mult5AfterAdd10 = value => 5 * (value + 10)
儘管這是一個很是簡單的例子,但仍然不想從頭編寫這個函數。首先,這裏可能會犯一個錯誤,好比忘記括號。第二,咱們已經有了一個加 10 的函數 add10 和一個乘以 5 的函數 mult5 ,因此這裏咱們就在寫已經重複的代碼了。
使用函數 add10,mult5 來重構 mult5AfterAdd10 :
var mult5AfterAdd10 = value => mult5(add10(value));
咱們只是使用現有的函數來建立 mult5AfterAdd10,可是還有更好的方法。
在數學中, f ∘ g 是函數組合,叫做「f 由 g 組合」,或者更常見的是 「f after g」。 所以 (f ∘ g)(x) 等效於f(g(x)) 表示調用 g 以後調用 f。
在咱們的例子中,咱們有 mult5 ∘ add10 或 「add10 after mult5」,所以咱們的函數的名稱叫作 mult5AfterAdd10。因爲Javascript自己不作函數組合,看看 Elm 是怎麼寫的:
add10 value = value + 10 mult5 value = value * 5 mult5AfterAdd10 value = (mult5 << add10) value
在 Elm 中 << 表示使用組合函數,在上例中 value 傳給函數 add10 而後將其結果傳遞給 mult5。還能夠這樣組合任意多個函數:
f x = (g << h << s << r << t) x
這裏 x 傳遞給函數 t,函數 t 的結果傳遞給 r,函數 t 的結果傳遞給 s,以此類推。在Javascript中作相似的事情,它看起來會像 g(h(s(r(t(x))))),一個括號噩夢。
你們都說簡歷沒項目寫,我就幫你們找了一個項目,還附贈【搭建教程】。
函數式語言中3個常見的函數:Map,Filter,Reduce。
以下JavaScript代碼:
for (var i = 0; i < something.length; ++i) { // do stuff }
這段代碼存在一個很大的問題,但不是bug。問題在於它有不少重複代碼(boilerplate code)。若是你用命令式語言來編程,好比Java,C#,JavaScript,PHP,Python等等,你會發現這樣的代碼你寫地最多。這就是問題所在。
如今讓咱們一步一步的解決問題,最後封裝成一個看不見 for 語法函數:
先用名爲 things 的數組來修改上述代碼:
var things = [1, 2, 3, 4]; for (var i = 0; i < things.length; ++i) { things[i] = things[i] * 10; // 警告:值被改變! } console.log(things); // [10, 20, 30, 40]
這樣作法很不對,數值被改變了!
在從新修改一次:
var things = [1, 2, 3, 4]; var newThings = []; for (var i = 0; i < things.length; ++i) { newThings[i] = things[i] * 10; } console.log(newThings); // [10, 20, 30, 40]
這裏沒有修改things數值,但卻卻修改了newThings。暫時先無論這個,畢竟咱們如今用的是 JavaScript。一旦使用函數式語言,任何東西都是不可變的。
如今將代碼封裝成一個函數,咱們將其命名爲 map,由於這個函數的功能就是將一個數組的每一個值映射(map)到新數組的一個新值。
var map = (f, array) => { var newArray = []; for (var i = 0; i < array.length; ++i) { newArray[i] = f(array[i]); } return newArray; };
函數 f 做爲參數傳入,那麼函數 map 能夠對 array 數組的每項進行任意的操做。
如今使用 map 重寫以前的代碼:
var things = [1, 2, 3, 4]; var newThings = map(v => v * 10, things);
這裏沒有 for 循環!並且代碼更具可讀性,也更易分析。
如今讓咱們寫另外一個常見的函數來過濾數組中的元素:
var filter = (pred, array) => { var newArray = []; for (var i = 0; i < array.length; ++i) { if (pred(array[i])) newArray[newArray.length] = array[i]; } return newArray; };
當某些項須要被保留的時候,斷言函數 pred 返回TRUE,不然返回FALSE。
使用過濾器過濾奇數:
var isOdd = x => x % 2 !== 0; var numbers = [1, 2, 3, 4, 5]; var oddNumbers = filter(isOdd, numbers); console.log(oddNumbers); // [1, 3, 5]
比起用 for 循環的手動編程,filter 函數簡單多了。最後一個常見函數叫reduce。一般這個函數用來將一個數列歸約(reduce)成一個數值,但事實上它能作不少事情。
在函數式語言中,這個函數稱爲 fold。
var reduce = (f, start, array) => { var acc = start; for (var i = 0; i < array.length; ++i) acc = f(array[i], acc); // f() 有2個參數 return acc; });
reduce函數接受一個歸約函數 f,一個初始值 start,以及一個數組 array。
這三個函數,map,filter,reduce能讓咱們繞過for循環這種重複的方式,對數組作一些常見的操做。但在函數式語言中只有遞歸沒有循環,這三個函數就更有用了。附帶提一句,在函數式語言中,遞歸函數不只很是有用,還必不可少。
代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug。
原文:https://blog.bitsrc.io/functi...
文章每週持續更新,能夠微信搜索【大遷世界 】第一時間閱讀,回覆【福利】有多份前端視頻等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已經收錄,歡迎Star。