即學即用系列一:純函式
最近一直在思考如何通過文章或者培訓快速提升團隊的編碼能力,總結下來其實技術的學習分為兩類:一種是系統性的學習,比如學習一門語言,學習一個開發框架,這更需要自己從入門到進階再到實踐一步步系統性的學習,單靠幾篇文章或者幾次培訓,效果並不明顯;還有一種是技巧性的學習,比如某些程式設計實踐、設計原則,其實並沒有多麼複雜,但是不知道就不會用,知道了就會有意識去用,就好比玩微信遊戲跳一跳,在某些方塊上停留一段時間就會獲得加分,不知道的時候根本想不到,知道了以後想不用都難。於是就有了《即學即用》這個系列的文章。
系列第一篇,就從純函式開始,由於我是前端方向,所以就從JavaScript語言中的純函式說起。
什麼是純函式
純函式是函數語言程式設計中非常重要的一個概念,簡單來說,就是一個函式的返回結果只依賴於它的引數,並且在執行過程中沒有副作用,我們就把這個函式叫做純函式。
下面我們來劃重點:
- 函式的返回結果只依賴於它的引數
- 函式執行過程中沒有副作用
首先來解釋第一點:函式的返回結果只依賴於它的引數
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
的返回結果只依賴於它的引數x
和b
,就是說,只要程式碼不變,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中常用的兩個方法slice
和splice
來舉一個例子:
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
可以看到,slice
和splice
的作用是大致相同的,但是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
不熟純函式,它依賴於getName
、getAge
兩個函式,如果我不小心改變了其中某個函式的功能,這將使得sayHi
這個函數出現錯誤。當網頁變得複雜,且由多人維護的時候,bug除錯會變得非常複雜。
使用純函式的優點
1. 可複用性
純函式僅依賴於傳入的引數,這意味著你可以隨意將這個函式移植到別的程式碼中,只需要提供踏需要的引數即可。如果是非純函式,有可能你需要一根香蕉,卻需要將整個香蕉樹搬過去。
2. 可測試性
純函式非常容易進行單元測試,因為不需要考慮上下文環境,只需要考慮輸入和輸出。
3. 並行程式碼
純函式是健壯的,改變執行次序不會對系統造成影響,因此純函式的操作可以並行執行。
總結
雖然純函式有很多優點,但也要避免濫用的情況。函式越純,對環境依賴越小,往往意味著要傳入更多的引數。我們的最終目的是:讓你的程式碼儘可能簡單易懂和靈活。這篇文章主要介紹了JavaScript中純函式的概念,但是在很多其他開發語言中,純函式的概念是一樣通用的,比如筆者正在自學的JAVA,歡迎大家針對各種語言中對純函式的理解和我一起討論。