概念
- 相擁的屬於永遠會得到相同的輸出,而且沒有任何可觀察的副作用。
- 函數式編程不會保留計算中間的結果,所以邊開那個是不可改變的(無狀態的)
- 可以吧一個函數的執行結果交給另外一個函數去處理
純函數就是類似數學中的函數(用來描述輸入和輸出的關係)y=f(x)
- lodash是一個純函數的功能庫,提供了對數組、數字、對象、字符串、函數等操作的一些方法
slice
返回數組部分的指定部分,不會改變原數組,並且輸入相同的值得到的總是相同的
const arr1 = [1, 2, 3]
console.log(arr1.slice(0,1)) // 1
console.log(arr1.slice(0,1)) // 1
console.log(arr1) // [ 1, 2, 3 ]
splice
對數組進行操作返回該數組,會改變原數組,輸入相同的值得到的總是不相同的
const arr2 = [1, 2, 3]
console.log(arr1.splice(0,1)) // 1
console.log(arr1.splice(0,1)) // 2
console.log(arr1) // [ 3 ]
實現一個自己的純函數 sum
function sum(n,m){
return n+m
}
sum(1+2) // 3
sum(1+2) // 3
loadsh
是一個一致性、模塊化、高性能的 JavaScript 實用工具庫
loadsh傳送門
簡單實用
首先需要下載loadsh
yarn && yarn add loadsh
開始使用
const _ = require('loadsh')
const arr = ['xiaoMing','liLei','xiaoHong']
// 返回傳入數組的第一個元素
console.log(_.first(arr)) // xiaoMing
// 返回傳入數組的第一個元素
console.log(_.head(arr)) // xiaoMing
// 返回傳入數組的最後一個元素
console.log(_.last(arr)) // xiaoHong
// 字符串轉大寫
console.log(_.toUpper(_.first(arr))) // XIAOMING
// 數組反轉
console.log(_.reverse(arr)) // [ 'xiaoHong', 'liLei', 'xiaoMing' ]
// each
_.each(arr,(val,index)=>{
console.log(val,index)
// xiaoHong 0
// liLei 1
// xiaoMing 2
})
console.log(_.includes(arr,'liLei')) // true
console.log(_.find(arr,(val)=>val==='liLei')) // liLei
console.log(_.findIndex(arr,(val)=>val==='liLei')) // 1
其實loadsh就是在es6之前封裝的函數庫。雖然現在es6及更高的版本已經出現,但是loadsh還是有學習的必要的,例如後面的函數柯里化、函數組合等等。
純函數優勢
可緩存
因爲函數對相同的輸入始終有相同的結果,所以可以吧函數的結果緩存起來
const _ = require('loadsh')
function getAtea(r){
console.log('getAtea函數執行')
return Math.PI*r*r
}
console.log(getAtea(2))
// getAtea函數執行
// 12.566370614359172
const memoizeGetAtea = _.memoize(getAtea)
console.log(memoizeGetAtea(2))
// getAtea函數執行
// 12.566370614359172
console.log(memoizeGetAtea(2))
// 此次執行memoizeGetAtea但是並沒有調用getAtea 而是執行上一次的計算結果
// 12.566370614359172
console.log(memoizeGetAtea(3))
// getAtea函數執行 因爲 半徑爲3圓的面積並沒有被緩存 所以會執行getAtea
// 28.274333882308138
console.log(memoizeGetAtea(3))
// 此次已經緩存了所以不會調用getAtea
// 28.274333882308138
模擬memoize
function memoize(fn){
let cache = {}
return function(){
let key = JSON.stringify(arguments)
cache[key] = cache[key] || fn.apply(undefined,arguments)
return cache[key]
}
}
function getAtea(r){
console.log('getAtea函數執行')
return Math.PI*r*r
}
const memoizeGetAtea = memoize(getAtea)
console.log(memoizeGetAtea(2))
// getAtea函數執行
// 12.566370614359172
console.log(memoizeGetAtea(2))
// 12.566370614359172
可測試(單元測試)
跑個題 實現一個最簡單單元測試及運行環境。
# 下載所需依賴
yarn add jest babel-jest babel-core babel-preset-env regenerator-runtime --dev
新建 .babelrc
{
"presets": [
"env"
]
}
修改package.json
{
"scripts": {
"test": "jest"
},
"dependencies": {
"loadsh": "^0.0.4"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-jest": "^26.0.1",
"babel-preset-env": "^1.7.0",
"jest": "^26.0.1",
"regenerator-runtime": "^0.13.5"
}
}
目錄結構如下
├── package-lock.json
├── package.json
├── src
│ └── index.js
├── test
│ └── index.test.js
└── yarn.lock
src/index.js 導出模塊
function add (n,m){
return n+m
}
function reduce (n,m){
return n-m
}
export {
add,
reduce
}
test/index.test.js
import {add,reduce} from '../src/index';
test('add(1 + 1) 等於 2', () => {
expect(add(1, 1)).toBe(2);
})
test('reduce(2 - 1) 等於 1', () => {
expect(reduce(2, 1)).toBe(1);
})
test('add(1 + 1) 等於 2', () => {
expect(add('1', 1)).toBe(2);
})
yarn test
並行處理
- 多線程並行操作共享的內存數據可能會出現意外情況
- 函數不需要訪問共享內存數據,所以並行環境下可以輸入任意運行純函數(web worker)
副作用
副作用讓一個函數變得不純,如果函數依賴於外部的狀態就無法保證相同的輸出,就會帶來副作用
// 不純函數
const mini = 18
function checkAge(age){
return age > mini
}
// 純函數(存在硬編碼,後續可以通過柯里化解決)
function checkAge2(){
const mini = 18
return age >= mini
}
副作用主要來源
- 配置文件
- 數據庫
- 獲取用戶輸入
所有的外部交互都有可能帶來副作用,副作用也使得方法通用性下降不適合擴展,同時副作用會給程序中帶來安全隱患給程序帶來不確定性,但是副作用不可能完全禁止,儘可能控制他們在可控範圍內發生。
ps:函數式編程+TypeScript食用更加