談JavaScript中純函數與非純函數

純函數是函數式編程的基礎,之前在優雅編碼的文章中提到過,多寫純函數,本文來簡單介紹一下純函數和非純函數的概念和區別。

純函數與非純函數

純函數:

當給定相同的輸入時,純函數總是返回一致的輸出,并且永遠不會產生超出函數范圍的效果,使它們可以預測。純函數不管執行多少次,結果都是可預測的。

  • 可預測的
  • 沒有副作用

非純函數

當給定相同的輸入時,不純函數可能不會返回一致的結果,并且它們可能會產生超出函數范圍的影響。

  • 不可預測的
  • 有副作用

可預測的

看看下面的例子:

const add = (a, b) => a + b;

console.log(add(2, 5));

對于當 a=2,b=3 的時候,函數 add 的結果可以預測到是 7 ,因此上面的函數可以說是純函數。

接下來看下面的代碼:

const add = (a, b) => a + b + parseInt(Math.random() * 10, 10);

console.log(add(2, 5));

現在這個函數已經不可預測了,因為增加了隨機數。

副作用

還是從代碼開始,你能預測輸出嗎?

const globalVar = 1;
const add = (a, b) => a + b + globalVar;
console.log(add(2, 5));

從當前來看,結果是可以預測的,為 8 ,看似是對的。

let globalVar = 1;
const add = (a, b) => a + b + globalVar;
globalVar = 0;
console.log(add(2, 5));

輸出為 7 ,看似也能預測,但受 globalVar 的影響,這種現象就稱為副作用。

前端開發中,其它有副作用的對象:

  1. Dom 操作:函數中存在 Dom 操作,因為 Dom 對象可以在很多地方修改。
const add = (a, b) => {
    document.write("hello");
    return a + b;
};
  1. 外部依賴:函數依賴于函數作用域之外的腳本庫。
let globalVar = 1;
const add = (a, b) => a + b + globalVar;
  1. console:因為 console 是外部 API 而不是 JavaScript 方法。
const add = (a, b) => {
    console.log(a + b);
};
  1. fetchpromise、 和任何形式的異步函數都是不純的,因為 JavaScript 本質上是同步的。

純函數優點

通過上面介紹純函數和非純函數的概念的特征,現在來看下為什么要盡量多寫純函數:

  • 更容易進行測試,結果只依賴輸入,測試時可以確保輸出穩定
  • 更容易維護和重構,可以寫出質量更高的代碼
  • 更容易調用,不用擔心函數會有什么副作用
  • 結果可以緩存,因為相同的輸入總是會得到相同的輸出