如何能通過增加代碼的健壯來提高程序的可靠性是每一個高級前端要面對的問題,函數式編程就是一個必備的利器。
函數式編程的概念
函數式編程是一種編程範式,是一種構建計算機程序結構和元素的風格,它把計算看作是對數學函數的評估,避免了狀態的變化和數據的可變。
與之相對應的就是“命令是編程”
例如:給數組每項取平方操作,命令式編程:詳細的命令機器去完成我們想要的結果;看例子:
let a = [1,2,3,4,5];
for(let i=0;i<a.length;i++){
a[i] = a[i]*a[i];
}
console.log(a);//[1, 4, 9, 16, 25]
上面這段代碼就是經典的命令式編程,它除了完成a的每項平方值外不能重用,離開了上下文環境它幾乎沒有任何作用。
如果我們將上面的代碼進行函數封裝:
let getSquare = (a)=>{
let result = [];
for(let i =0;i<a.length;i++){
result.push(a[i]*a[i]);
}
return result;
}
getSquare([2,3,4,5])
(4) [4, 9, 16, 25]
雖然我們將求數組的平方方法進行了封裝,可以滿足其他地方使用,但是如果此時需求更改爲求每項平方數值再加1,那麼這個方法就有廢了,要不就需要修改方法體的代碼,顯然不夠靈活,依然是命令式編程。
如果應用函數式編程去處理上面問題:
對問題進行函數式分析,有一個數組,我們需要對數組的每一項值進行計算,然後輸出新的數組。
let a = [1,2,3,4,5];
let hadle = (a,fn)=>{
let result = [];
for(let i =0;i<a.length;i++){
result.push(fn(a[i]));
}
return result;
}
let calculate = (v)=>{
return v*v+1;
}
let value = hadle(a,calculate);
console.log(value)//[2, 5, 10, 17, 26]
可以看到這樣處理,當需求再次變化爲求每項平方值減1時,我們只需要修改calculate方法即可其餘不變,增強了代碼的穩定性。
函數式編程主張
將複雜的問題用多個函數分解爲多個簡單的函數,然後再組合調用,這樣做的好處就是保留不變函數,通過儘量少的可變函數來增加程序穩定性。
因爲我們要對複雜問題進行分解,分解的標準是什麼:
純函數
判斷一個函數是否是純函數:
- 如果給定相同的參數,則返回相同的結果(也稱爲確定性),僅僅依賴於輸入的參數。
- 它不會引起任何副作用。
看例子:商品打折計算
let discount = 0.8;
let calculatePrice = (price,num)=>{
price*num*discount;
}
let price = calculatePrice(20,2)
顯然上面calculatePrice方法不是純函數,因爲它除了依賴商品單價price和個數num外還依賴了打折率discount,如果作爲全局變量的discount變化了0.9,即使相同的price和num輸出的結果是不同的。我們需要再次更改將discount變爲參數
let discount = 0.8;
let calculatePrice = (price,num, discount)=>{
price*num*discount;
}
let price = calculatePrice(20,2,discount)
這樣calculatePrice方法就是純函數,除了三個參數的依賴在沒有其他依賴,與此同時沒有對源程序造成任何副作用。
我們看一個產生副作用的例子:
let discount = 0.8;
let numObj = {
numArry:[1,2,3]
}
let calculatePrice = (price,numObj, discount)=>{
let num = 0;
for(let i=0;i<numObj.numArry.length;i++){
num+=numObj.numArry[i];
}
numObj.numArry = [];
return price*num*discount;
}
let price = calculatePrice(20,numObj,discount)
console.log(price)
console.log(numObj)
結果:
上面的代碼雖然做到了除參數不依賴其他,但是執行方法後我們更改了外部變量numObj的值(引用傳值) 即對外部產生了副作用。
也不能稱爲嚴格意義上的純函數。
再例如js數組的方法 :
不會改變原來數組的純函數有:
concat()—連接兩個或更多的數組,並返回結果。
every()—檢測數組元素的每個元素是否都符合條件。
some()—檢測數組元素中是否有元素符合指定條件。
filter()—檢測數組元素,並返回符合條件所有元素的數組。
indexOf()—搜索數組中的元素,並返回它所在的位置。
join()—把數組的所有元素放入一個字符串。
toString()—把數組轉換爲字符串,並返回結果。
lastIndexOf()—返回一個指定的字符串值最後出現的位置,在一個字符串中的指定位置從後向前搜索。
map()—通過指定函數處理數組的每個元素,並返回處理後的數組。
slice()—選取數組的的一部分,並返回一個新數組。
valueOf()—返回數組對象的原始值。
-----------分割線-------------------
會改變原來數組的有:
pop()—刪除數組的最後一個元素並返回刪除的元素。
push()—向數組的末尾添加一個或更多元素,並返回新的長度。
shift()—刪除並返回數組的第一個元素。
unshift()—向數組的開頭添加一個或更多元素,並返回新的長度。
reverse()—反轉數組的元素順序。
sort()—對數組的元素進行排序。
splice()—用於插入、刪除或替換數組的元素。
可變性與不可變性
可變性指的是一個變量創建之後可以被任意修改,
不可變性指的是變量被創建後不能再被修改,是函數式編程追求的核心目標。
例如要解決引用數據類型不可變性的問題,我們就引申出對象的深拷貝,後面的學習中會對深拷貝問題展開詳細說明。
總結
函數式編程是一種思想,完全做到純函數式的編程是幾乎不可能的,但作爲高級開發人員必須要時刻提醒自己用函數式去解決問題,這是成長的必然之路,vue react框架組建思想都是基於函數式編程的,理解他對於閱讀源碼有很大幫助。