JavaScript 中的純函式
前言
純函式是一個常見的概念,在日常工作中也經常會遇到,它其實非常簡單,今天我們來了解一下它的好處以及為什麼要使用它。
兩個特點
一個函式,如果符合以下兩個特點,那麼它就可以稱之為純函式:
- 對於相同的輸入,永遠得到相同的輸出
- 沒有任何可觀察到的副作用
相同輸入得到相同輸出
我們先來看一個不純的反面典型:
let greeting = 'Hello'
function greet (name) {
return greeting + ' ' + name
}
console.log(greet('World')) // Hello World
上面的程式碼中,greet('World'),是不是永遠返回Hello World? 顯然不是,假如我們修改greeting的值,就會影響greet函式的輸出。即函式greet其實是依賴外部狀態
那我們做以下修改:
function greet (greeting, name) {
return greeting + ' ' + name
}
console.log(greet('Hi', 'Savo')) // Hi Savo
將greeting引數也傳入,這樣對於任何輸入引數,都有與之對應的唯一的輸出引數了,該函式就符合了第一個特點。
沒有副作用
副作用的意思是,這個函式的執行,不會修改外部的狀態。
下面再看反面典型:
const user = {
username: 'savokiss'
}
let isValid = false
function validate (user) {
if (user.username.length > 4) {
isValid = true
}
}
可見,執行函式的時候會修改到isValid的值(注意:如果你的函式沒有任何返回值,那麼它很可能就具有副作用!)
那麼我們如何移除這個副作用呢?其實不需要修改外部的isValid變數,我們只需要在函式中將驗證的結果return出來:
const user = {
username: 'savokiss'
}
function validate (user) {
return user.username.length > 4;
}
const isValid = validate(user)
這樣validate函式就不會修改任何外部的狀態了~
電腦刺繡繡花廠 http://www.szhdn.com
為什麼要用純函式?
你可能聽過純函式有不少優點,如果你經手過各種難維護的函式,你就更應該考慮使用純函式。
可測試性(Testable)
讓我們先用不純的greet方法來做單元測試:
// jest 語法
describe('greet', function() {
it('shows a greeting', function() {
expect(greet('Savo')).toEqual('Hello Savo')
});
});
如果我們修改了greeting變數為Hi,上面的測試就會失敗了,這本質上不應該發生。
那我們如果換成純函式版本的greet,所有都是那麼自然~ 只需要修改單元測試中傳入的引數即可!
可快取性(Cacheable)
純函式可以根據輸入來做快取。實現快取的是一種叫作memorize的技術。
下面的程式碼來自vue原始碼:
/**
* Create a cached version of a pure function.
* 只適用於快取 接收一個字串為引數的 fn
*/
export function cached (fn) {
const cache = Object.create(null)
return function cachedFn (str) {
const hit = cache[str]
return hit || (cache[str] = fn(str))
}
}
/**
* Capitalize a string.
*/
export const capitalize = cached((str) => {
return str.charAt(0).toUpperCase() + str.slice(1)
})
capitalize即為快取後的函式,如果多次呼叫就會返回快取後的值,從而節省計算資源,而這一切的前提都建立在傳入cached中的那個函式為純函式的基礎上。
可移植性 / 自文件化(Portable / Self-Documenting)
由於純函式是自給自足的,它需要的東西都在輸入引數中已經宣告,所以它可以任意移植到任何地方。
並且純函式對於自己的依賴是誠實的,這一點你看它的形參就知道啦~正所謂形參起的好,註釋不用搞~(雙押!)純函式就是這麼個正直的小可愛~