函數式編程基本知識#
為什麼要學函數式編程?#
在過去,他是命令式編程佔據主流的。命令式編程是顆粒度最小的編程,它可以精確的控制每個元素的狀態。
但是顆粒度小既意味著自由度高,也意味著複雜。因為顆粒度高所以你如果想要實現一個簡單常用的功能,你必須從頭開始去構建。很多基礎設施都沒有辦法重用。
有一些函數是可以重用的,比如常見的比大小,排序,過濾等等。這些函數是可以重用的,但是在命令式編程中,這些函數都是附屬品,而不是核心。
函數式編程是一種聲明式的編程範式,它的核心是函數。函數式編程的核心是函數,而不是狀態。
OK,所以函數式編程本質上學的是什麼?如若說思想的話,那就是分層。將一個核心函數分成一個個附屬函數的過程。如若說實現的話,說的就是附屬函數的 API 語法。
什麼是柯里化?#
如果說函數式編程中有兩種操作是必不可少的那無疑就是柯里化(Currying)和函數組合(Compose),柯里化其實就是流水線上的加工站,函數組合就是我們的流水線,它由多個加工站組成。
f(a,b,c) → f(a)(b)(c)
我們嘗試寫一個 curry 版本的 add 函數
var add = function(x) {
return function(y) {
return x + y;
};
};
const increment = add(1);
increment(10); // 11
為什麼這個單元函數很重要?還記得我們之前說過的,函數的返回值,有且只有一個嘛?
如果我們想順利的組裝流水線,那我就必須保證我每個加工站的輸出剛好能流向下個工作站的輸入。因此,在流水線上的加工站必須都是單元函數。
現在很好理解為什麼柯里化配合函數組合有奇效了,因為柯里化處理的結果剛好就是單輸入的。
函數式編程的相關語法#
JavaScript 中的函數式編程支持多種實用的函數,主要包括但不限於以下幾類:
-
映射(Map):對數組中的每個元素應用一個函數,並創建一個新數組,該數組包含應用函數後的結果。
const numbers = [1, 2, 3, 4]; const doubled = numbers.map(x => x * 2); // [2, 4, 6, 8]
-
過濾(Filter):根據測試函數確定是否保留數組中的元素,創建一個包含通過測試的元素的新數組。
const numbers = [1, 2, 3, 4]; const evens = numbers.filter(x => x % 2 === 0); // [2, 4]
-
歸約(Reduce):將數組元素組合起來,通過 reducer 函數減少為單個值。
Array.prototype.reduce()
方法在 JavaScript 中是一個非常強大的工具,用於將數組中的所有元素歸並(或者說 “減少”)為單個值。這個方法對於累加數組中的值、合併數組內容或者在單個遍歷中實現複雜的數據轉換等操作非常有用。reduce
方法的基本用法如下:array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue)
就是回調函數的默認參數,callback:執行數組中每個值的函數,包含四個參數:
accumulator:累加器累加回調的返回值;它是上一次調用回調時返回的累積值,或者是提供的初始值(initialValue)。
currentValue:數組中正在處理的元素。
currentIndex(可選):數組中正在處理的當前元素的索引。如果提供了 initialValue,則起始索引號為 0,否則為 1。
array(可選):調用 reduce 方法的數組。initialValue(可選):作為第一次調用 callback 函數時第一個參數(accumulator)的值。如果沒有提供初始值,則將使用數組的第一個元素。
const numbers = [1, 2, 3, 4]; const sum = numbers.reduce((accumulator, current) => accumulator + current, 0); // 10
-
每個(Every):檢查數組中的所有元素是否都符合提供的測試函數。
無論如何 every 返回的都是布爾值,如果他接受的不是布爾值,他會將其轉換成布爾值進行運算。
在 JavaScript 中,Array.prototype.every()
方法確實期望一個函數作為參數,這個函數對數組中的每個元素執行測試。如果回調函數對每個元素都返回一個真值(truthy value),every
方法才會返回true
;否則返回false
。
這個回調函數不一定非得返回布爾值(true
或false
)。JavaScript 中的任何值都可以被視為真值或假值(falsy value)。如果回調函數返回的值可以被轉換成布爾上下文中的true
,那麼這個值就被認為是真值。常見的假值包括0
、null
、undefined
、NaN
、空字符串""
和false
本身。除此之外的所有值都被認為是真值。
例如,如果回調函數返回數字1
,儘管1
不是布爾值,但它是一個真值,因此every
會將其解釋為true
。如果回調函數返回0
(一個假值),every
會將其解釋為false
。const numbers = [1, 2, 3, 4]; const allPositive = numbers.every(x => x > 0); // true
-
某個(Some):檢查數組中是否至少有一個元素符合提供的測試函數。也與 every 同理。
const numbers = [1, 2, 3, 4]; const hasNegative = numbers.some(x => x < 0); // false
-
查找(Find):返回數組中滿足提供的測試函數的第一個元素的值。
const numbers = [1, 2, 3, 4]; const firstEven = numbers.find(x => x % 2 === 0); // 2
-
查找索引(FindIndex):返回數組中滿足提供的測試函數的第一個元素的索引。
const numbers = [1, 2, 3, 4]; const indexOFEven = numbers.findIndex(x => x % 2 === 0); // 1
-
排序(Sort):對數組的元素進行排序,並返回排序後的數組。
const numbers = [4, 2, 3, 1]; numbers.sort((a, b) => a - b); // [1, 2, 3, 4]
-
切片(Slice):返回數組的一部分,不修改原數組。返回一個新的數組,包含從 start(包括該元素) 到 end (不包括該元素)的 arrayObject 中的元素。
const numbers = [1, 2, 3, 4]; const middleTwo = numbers.slice(1, 3); // [2, 3]
這些函數是函數式編程在 JavaScript 中的基本工具,它們提供了一種聲明式和不可變的編程範式,有助於寫出更清晰、更易於維護的代碼。
現在讓我們去默寫一遍這些功能。
簡單默寫一下你熟悉的函數式 JS,API
- every, 他的作用是將數組的每個 API 去執行一下回調,如果數組的所有元素回調返回的值為 true,那它就會返回為 true,如果有一個不為 true 就返回 false。
- same,和 every 同理,如果有一個返回為 true 就為 true,都返回 false 就返回 false。
- map,他的作用是映射,就是將一個數組裡的每個數變成回調函數的返回值,然後最後組合起來返回一個數組。
- filter,他的作用是過濾,就是將滿足回調函數的數保留起來,不滿足回調函數,也就是回調函數返回 false 的數就刪除,他接受的回調函數也是要接受一個布爾類型的。
- find,他的作用是將數組裡每個數進行查詢,如果數組的數滿足回調函數,就返回這個數,回調接受的也是個布爾類型。
- findindex,和 find 一樣不過它返回的是這個數的索引。
- sort,排序,將數組的數按規則排序,怎麼按規則排序呢?他接受兩個數,如果返回負數,那就是 a 在前,如果返回正數,那就是 b 在前,如果返回 0,那就是不變。最後也是返回排序後的數組。
- slice,切片,他接受兩個參數,不接受回調。它的兩個參數,一個是開始的元素一個是結束的元素,返回 [,) 的一个數組,就是不包括結束元素的數組。
- reduce,他是將整個數組歸一化的函數,他可以讓一數組變成一個數,簡單的應用比如可以做累加器。也可以做數組變成列表的操作。他接受兩個參數,一個是回調函數,一個是初始值,回調函數第一個值是之前的返回值,第二個值是當前值。
這些其實不只是函數化編程,更多的其實是 Array 的一些對象方法。那除此之外還有什麼對象方法呢?#
concat()
:連接兩個或更多的數組,並返回一個新的數組。copyWithin()
:在數組內部,將一系列元素從指定位置複製到另一個指定位置,並返回修改後的數組。entries()
:返回一個新的 Array Iterator 對象,該對象包含數組中每個索引的鍵 / 值對。every()
:測試數組的所有元素是否都通過了指定函數的測試。fill()
:使用一個固定值來填充數組中從起始索引到終止索引的全部元素。filter()
:創建一個新數組,其包含通過所提供函數實現的測試的所有元素。find()
:返回數組中滿足提供的測試函數的第一個元素的值。findIndex()
:返回數組中滿足提供的測試函數的第一個元素的索引。forEach()
:對數組的每個元素執行一次提供的函數。from()
:從類數組或可迭代對象中創建一個新數組實例。includes()
:判斷一個數組是否包含一個指定的值,根據情況,如果包含則返回true
,否則返回false
。indexOf()
:返回在數組中可以找到一個給定元素的第一個索引,如果不存在,則返回 - 1。isArray()
:判斷傳入的參數是否是一個數組。join()
:將數組中的所有元素連接成一個字符串並返回。keys()
:返回一個包含數組中每個索引鍵的 Array Iterator 對象。lastIndexOf()
:返回指定元素在數組中的最後一個的索引,如果不存在則返回 -1。map()
:創建一個新數組,其結果是該數組中的每個元素是調用一次提供的函數後的返回值。pop()
:從數組中刪除最後一個元素,並返回該元素的值。push()
:向數組的末尾添加一個或更多元素,並返回新的長度。reduce()
:對累加器和數組中的每個元素(從左到右)應用一個函數,將其減少為單個值。reduceRight()
:接受一個函數作為累加器和數組的每個值(從右到左),將其減少到單個值。reverse()
:顛倒數組中元素的位置,第一個元素成為最後一個元素,最後一個元素成為第一個元素。shift()
:從數組中刪除第一個元素,並返回該元素的值。slice()
:返回一個由開始和結束(不包括結束)決定的淺拷貝數組的一部分,並以新數組對象的形式返回。some()
:測試數組中的某些元素是否通過由提供的函數實現的測試。sort()
:對數組元素進行排序,並返回數組。splice()
:通過刪除現有元素和 / 或添加新元素來更改一個數組的內容。toString()
:返回一個字符串,表示指定的數組及其元素。unshift()
:向數組的開頭添加一個或更多元素,並返回新的長度。valueOf()
:返回數組對象的原始值。
新增方法:Array.of()
:創建一個具有可變數量參數的新數組實例,而不考慮參數的數量或類型。Array.at()
:接受一個整數值並返回該索引處的元素,允許使用正數和負數索引。Array.flat()
:根據指定的深度遞歸進入數組,並將所有元素與遍歷到的子數組中的元素合併為一個新數組。Array.flatMap()
:首先使用映射函數映射每個元素,然後將結果壓縮成一個新數組。