1. 程式人生 > 其它 >即學即用系列一:純函式

即學即用系列一:純函式

最近一直在思考如何通過文章或者培訓快速提升團隊的編碼能力,總結下來其實技術的學習分為兩類:一種是系統性的學習,比如學習一門語言,學習一個開發框架,這更需要自己從入門到進階再到實踐一步步系統性的學習,單靠幾篇文章或者幾次培訓,效果並不明顯;還有一種是技巧性的學習,比如某些程式設計實踐、設計原則,其實並沒有多麼複雜,但是不知道就不會用,知道了就會有意識去用,就好比玩微信遊戲跳一跳,在某些方塊上停留一段時間就會獲得加分,不知道的時候根本想不到,知道了以後想不用都難。於是就有了《即學即用》這個系列的文章。

系列第一篇,就從純函式開始,由於我是前端方向,所以就從JavaScript語言中的純函式說起。

什麼是純函式

純函式是函數語言程式設計中非常重要的一個概念,簡單來說,就是一個函式的返回結果只依賴於它的引數,並且在執行過程中沒有副作用,我們就把這個函式叫做純函式。

下面我們來劃重點:

  1. 函式的返回結果只依賴於它的引數
  2. 函式執行過程中沒有副作用

首先來解釋第一點:函式的返回結果只依賴於它的引數

const a = 1
const impure = (b)=>a + b
impure(2) // 3

上面程式碼中,impure函式不是一個純函式,因為它的返回結果依賴外部變數a,因為a是有可能變化的,所以我們不能保證impure(2)的值永遠是3。雖然impure函式的程式碼沒有變化,傳入的引數也沒有變化,但它的返回值是不可預料

的。我們再來改寫一下:

const a = 1
const pure = (x, b) => x + b
pure(1,2) //3

現在,pure的返回結果只依賴於它的引數xb,就是說,只要程式碼不變,pure(1, 2)的返回值永遠是3。

這就是純函式的第一個條件:函式的返回結果只依賴於它的引數

接下來解釋第二點:函式執行中沒有副作用

副作用是指:在計算結果的過程中,系統狀態的一種變化,或者與外部世界進行的可觀察的互動。我們再看一個例子:

var values = { a: 1 };

function impureFunction ( items ) {
  var b = 1;

  items.a = items.a * b + 2;

  return items.a;
}

var c = impureFunction( values );
values.a // 3

在上面的程式碼中,我們改變了引數物件中的一個屬性。由於我們定義的函式改變的物件在我們的函式作用域之外,導致這個函式成為“不純”的函式。

var values = { a: 1 };

function pureFunction ( a ) {
  var b = 1;

  a = a * b + 2;

  return a;
}

var c = pureFunction( values.a );
values.a // 1

上面的程式碼,我們只計算了作用域內的區域性變數,沒有任何作用域外部的變數被改變,因此這個函式是“純函式”。

除了修改外部的變數,一個函式在執行過程中還有很多方式產生外部可觀察的變化,比如說呼叫 DOM API 修改頁面,或者你傳送了 Ajax 請求,還有呼叫 window.reload重新整理瀏覽器,甚至是 console.log 往控制檯列印資料也是副作用。

純函式很嚴格,也就是說你幾乎除了計算資料以外什麼都不能幹,計算的時候還不能依賴除了函式引數以外的資料。

我們再來用JavaScript中常用的兩個方法slicesplice來舉一個例子:

var array1 = [0,1,2,3,4,5,6];
var array2 = [0,1,2,3,4,5,6];

var spliceArray = array1.splice(0,2);
var sliceArray = array2.slice(0,2);

console.log('array1: ' + array1);
console.log('spliceArray: ' + spliceArray);

console.log('array2: ' + array2);
console.log('sliceArray: ' + sliceArray); 

執行結果:

array1: 2,3,4,5,6
spliceArray: 0,1
array2: 0,1,2,3,4,5,6
sliceArray: 0,1

可以看到,slicesplice的作用是大致相同的,但是splice改變了原陣列,而slice卻沒有,實際開發中,slice這種不改變原陣列的方式更安全一些,改變原始陣列,是一種副作用

非純函式帶來的副作用

既然我們推薦純函式,那麼肯定是因為非純函式有缺陷。我們看下面的程式碼:

function getName(obj){
    return obj.name;
}
function getAge(obj){
  return obj.age;
}
function sayHi(person){
  console.log('I am' + getName(person) + ',and I am' + getAge(person) + 'years old');
}

var Tom = {
  name: 'TOM',
  age: 26
};

sayHi(Tom);

我們說sayHi不熟純函式,它依賴於getNamegetAge兩個函式,如果我不小心改變了其中某個函式的功能,這將使得sayHi這個函數出現錯誤。當網頁變得複雜,且由多人維護的時候,bug除錯會變得非常複雜。

使用純函式的優點

1. 可複用性

純函式僅依賴於傳入的引數,這意味著你可以隨意將這個函式移植到別的程式碼中,只需要提供踏需要的引數即可。如果是非純函式,有可能你需要一根香蕉,卻需要將整個香蕉樹搬過去。

2. 可測試性

純函式非常容易進行單元測試,因為不需要考慮上下文環境,只需要考慮輸入和輸出。

3. 並行程式碼

純函式是健壯的,改變執行次序不會對系統造成影響,因此純函式的操作可以並行執行。

總結

雖然純函式有很多優點,但也要避免濫用的情況。函式越純,對環境依賴越小,往往意味著要傳入更多的引數。我們的最終目的是:讓你的程式碼儘可能簡單易懂和靈活。這篇文章主要介紹了JavaScript中純函式的概念,但是在很多其他開發語言中,純函式的概念是一樣通用的,比如筆者正在自學的JAVA,歡迎大家針對各種語言中對純函式的理解和我一起討論。