(譯) 函數式 JS #2: 函數!

close-up photo of factory
"close-up photo of factory" by Taton Moïse on Unsplash

原文連接 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是純函數。它不使用任何超出其控制範圍的變量,它是肯定性的,咱們能夠很是有信心地說它將始終爲相同的輸入參數組合返回相同的結果。

而後呢?

爲何這一切很重要?有如下一些緣由代表純函數比非純函數更有優點:

  1. 更易閱讀

你只須要讀一下它的函數體就知道它作了哪些事情。

  1. 更易理解

不須要查找外部依賴,函數被調用的上下文等。這些對於純函數都沒有任何影響。

  1. 更易測試

若是你想測試一個純函數,你只須要用一些參數調用它,看看結果是不是你想要的結果。根本無需複雜的設置。

  1. 更高效

若是咱們知道對於給定的輸入,函數將始終產生相同的輸出,咱們就能夠緩存(memoize)它的結果,這樣咱們就沒必要在每次調用這個函數的時候都從新計算它。

使用純函數可使代碼更易於維護 - 由於它能夠更輕鬆地管理反作用。在接下來的部分中,咱們將瞭解反作用是什麼以及爲何,遺憾的是,計算機程序中不可能所有都是純函數。

如今咱們知道了什麼是純函數,讓咱們關注下一個與函數相關的術語:做爲一等公民的函數

一等公民函數

與「純函數」不一樣,「一等公民函數」在平常工做中並非一個很實用的概念。可是,在考慮編程語言的特性時,它就頗有用了。

若是在一個編程語言中,函數能夠像使用其餘的值一樣的方式使用,那麼你就能夠說這個語言具備「一等公民的函數」,也就是說:

  1. 它們能夠被傳遞,
  2. 它們能夠被分配給變量,
  3. 它們能夠被存儲在更復雜的數據結構中,如數組或對象。

能夠說沒有一等公民的函數,就沒有函數式編程(至少會很是的尷尬)。下面這個例子,說明爲何函數在 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)不接受或者返回咱們所知道的那些常規的值。他們在操做函數。

你可能已經熟悉了一些高階函數,例如:

  • map,
  • reduce,
  • filter,
  • compose,
  • forEach,
  • … 和別的。

函數式編程就是將一些小型,可重用和通用的函數組合成更復雜的函數。所以,在後面的討論中你將會看到更多不一樣的高階函數。


那麼,這就是咱們開始 FP 之旅所需的全部與函數相關的基本術語了。

下一章,咱們將關注函數式編程中的狀態 (state) - 如何管理它,以及如何避免它帶來的問題等等。咱們已經提到過一些關於狀態的內容(在討論純函數的時候),後面還有更多!

咱們已經學到了很多東西,但願你和我同樣對下一章感到興奮!

相關文章
相關標籤/搜索