原文連接 By: Krzysztof Czernek程序員
這是 "函數式 JS" 系列的第二篇。查看第一篇編程
如今咱們知道爲何學習函數式編程實踐能夠幫助你成爲一個更好的程序員了,那讓咱們開始一些有趣的東西吧。數組
在這一部分中,咱們將重點關注與函數式編程相關的術語和基本概念。緩存
遺憾的是,此次不會涉及不少代碼。從好的方面來講,一旦咱們理解了術語,咱們就可以方便地討論更復雜的主題。數據結構
毋庸置疑,函數式編程中最重要的就是函數。app
咱們都知道什麼是函數。它基本上就是一個(大多數時候都會有一個名字的)代碼片斷。dom
function add (x, y) {
return x + y
}
複製代碼
然而,當談到函數式編程時,咱們更想從一個特殊的角度來看待函數:數學中的函數。編程語言
即便咱們沒必要真的去使用代數,咱們也不得不認可函數式編程深深植根於數學。函數式編程
在簡單的數學術語中,函數是一種在給定輸入的狀況下產生特定輸出的機器。函數
有趣的是,給定輸入只能有一個輸出。這意味着,若是咱們爲函數提供相同的輸入,咱們但願它始終作一樣的事情,而且返回相同的值。
這聽起來沒什麼大不了,但實際上這是一個很強的限制。這個數學定義有着深遠的影響:
知足這些標準的函數在編程中稱爲純函數,它們對於函數式範式相當重要。
讓咱們看一下 JavaScript 中的一些函數示例,直接體會一下什麼是純函數。
function coin () {
return Math.random() < 0.5 ? 'heads' : 'tails'
}
複製代碼
Coin
不是純函數,由於它在給定相同輸入(null)的狀況下並不老是產生相同的結果 - 它不是肯定性的。
let firstName = 'krzysztof'
function uppercaseName (lastName) {
return `${firstName.toUpperCase()} ${lastName.toUpperCase()}`
}
複製代碼
uppercaseName
不是純函數,由於它依賴於一個不受其控制的變量。咱們沒法肯定在給定相同參數的狀況下它總會產生相同的結果。
let user = {
firstName: 'Krzysztof',
age: '26'
}
function happyBirthday () {
user.age = user.age + 1
}
複製代碼
happyBirthday
不是純函數,由於它不只訪問了一個不受控制的變量,還不會返回任何內容。
function calculatePrice (unitPrice, noOfUnits, couponValue = 0) {
return unitPrice * noOfUnits - couponValue;
}
複製代碼
calculatePrice
是純函數。它不使用任何超出其控制範圍的變量,它是肯定性的,咱們能夠很是有信心地說它將始終爲相同的輸入參數組合返回相同的結果。
爲何這一切很重要?有如下一些緣由代表純函數比非純函數更有優點:
你只須要讀一下它的函數體就知道它作了哪些事情。
不須要查找外部依賴,函數被調用的上下文等。這些對於純函數都沒有任何影響。
若是你想測試一個純函數,你只須要用一些參數調用它,看看結果是不是你想要的結果。根本無需複雜的設置。
若是咱們知道對於給定的輸入,函數將始終產生相同的輸出,咱們就能夠緩存(memoize)它的結果,這樣咱們就沒必要在每次調用這個函數的時候都從新計算它。
使用純函數可使代碼更易於維護 - 由於它能夠更輕鬆地管理反作用。在接下來的部分中,咱們將瞭解反作用是什麼以及爲何,遺憾的是,計算機程序中不可能所有都是純函數。
如今咱們知道了什麼是純函數,讓咱們關注下一個與函數相關的術語:做爲一等公民的函數。
與「純函數」不一樣,「一等公民函數」在平常工做中並非一個很實用的概念。可是,在考慮編程語言的特性時,它就頗有用了。
若是在一個編程語言中,函數能夠像使用其餘的值一樣的方式使用,那麼你就能夠說這個語言具備「一等公民的函數」,也就是說:
能夠說沒有一等公民的函數,就沒有函數式編程(至少會很是的尷尬)。下面這個例子,說明爲何函數在 JavaScript 中是一等公民:
function add (a, b) {
return a + b
}
function multiply (a, b) {
return a * b
}
const operations = { // 這裏咱們把函數當成普通的值使用
add,
multiply
}
operations.add(1, 2)
複製代碼
正如上面所說,JavaScript 的函數能夠在不一樣的函數之間傳遞。可是......這麼作的目的是什麼呢?
嗯,將函數傳入和傳出到另一個函數是函數式編程中的常見作法 - 並且功能很是強大。它給咱們引入了...
能夠「操做」其餘函數的函數被稱爲高階函數。這裏的操做,意思是指他們能夠作到下面兩點中的一個或兩個:
這個例子在 JavaScript 世界中很常見。其中一個示例是標準庫中的 Array.prototype.map 函數。它須要一個函數做爲參數並將其應用於數組中的每一個元素:
const numbers = [1, 1, 2, 3, 5, 8]
const transformFunction = x => x + 2
numbers.map(transformFunction)
複製代碼
下面是一個返回函數的函數,這個示例稍顯刻意:
function makeGreeter (greeting) {
return function greet (name) {
return `${greeting}, ${name}!`
}
}
// 或者使用 ES6 的語法:
const makeGreeter = greeting => name => `${greeting}, ${name}!`
const greet = makeGreeter('Hello')
console.log(greet('Krzysztof'))
複製代碼
你能夠看到,這些函數(map 和 makeGreeter)不接受或者返回咱們所知道的那些常規的值。他們在操做函數。
你可能已經熟悉了一些高階函數,例如:
函數式編程就是將一些小型,可重用和通用的函數組合成更復雜的函數。所以,在後面的討論中你將會看到更多不一樣的高階函數。
那麼,這就是咱們開始 FP 之旅所需的全部與函數相關的基本術語了。
下一章,咱們將關注函數式編程中的狀態 (state) - 如何管理它,以及如何避免它帶來的問題等等。咱們已經提到過一些關於狀態的內容(在討論純函數的時候),後面還有更多!
咱們已經學到了很多東西,但願你和我同樣對下一章感到興奮!