我們一直在使用javascript的for循環。但現在,在最新的函數式程式設計技巧的支援下,過時的它應該退休了。幸運的是,你不必是一個函數式程式設計大師,也可以做出這個改變。更幸運的是,這就是你在眼前項目中可以立馬做的事情!
我們一直在使用javascript的for循環。但現在,在最新的函數式程式設計技巧的支援下,過時的它應該退休了。
幸運的是,你不必是一個函數式程式設計大師,也可以做出這個改變。更幸運的是,這就是你在眼前項目中可以立馬做的事情!
那到底javascript的for循環有什麼問題?
for循環設計本身鼓勵改變狀态以及産生副作用,這兩者都是導緻錯誤和不可預知代碼的隐患。
我們都知道全局狀态是糟糕的,應該避免。可是,局部狀态和全局狀态一樣糟糕,隻是因為局部狀态處于一個較小的尺度中,沒有引起注意。是以,我們從來沒有真正解決問題,而是盡量把問題最小化。
對于可變的狀态,在一些未知的時間點,變量會因為某些未知的原因而改變。這時,你要花上數小時進行調試,尋找這個值改變的原因。光為這,我都不知道自己掉了多少把頭發了。
下面,我想簡單談談副作用。這個詞聽起來就煩人,副作用……媽蛋!難道你會希望自己的程式有什麼副作用?當然不!
但什麼是副作用?
當一個函數修改了其作用域以外的某些東西時,它就被視為有副作用。可以是改變一個變量的值、讀取鍵盤輸入、調用某個api、寫入磁盤資料、列印日志,等等。
副作用很強大,但同時也被承擔着重大的責任。
副作用應該盡可能被消除,或者封裝在内部,或可控。有副作用的函數難以測試,也難以推斷,是以要盡你所能甩掉它。幸好這裡可以不用擔心副作用。
好了,閑話少說,上代碼。我們看一下這段或許你已經看過上千次典型的for循環:
const cats = [
{ name: 'mojo', months: 84 },
{ name: 'mao-mao', months: 34 },
{ name: 'waffles', months: 4 },
{ name: 'pickles', months: 6 }
]
var kittens = []
// 典型的拙劣寫法:for循環
for (var i = 0; i < cats.length; i++) {
if (cats[i].months < 7) {
kittens.push(cats[i].name)
}
}
console.log(kittens)
我計劃将這些代碼一步一步重構,讓你清楚地看到将這些代碼轉換成更漂亮的寫法是多麼容易。
第一個改變就是把if裡的聲明抽象為一個函數:
const iskitten = cat => cat.months < 7
if (iskitten(cats[i])) {
通常,抽出if語句是個不錯的做法。過濾的着眼點從“小于7個月”轉變為“是一隻小貓”非常非常重要。現在,當你再看這段代碼,意圖就變得清晰了。為什麼要取得小于7個月的貓?一點都不明确。我們的意圖是找到小貓,是以讓代碼表示出來!
另一個好處是iskitten現在可複用了,而且我們都明白:
讓代碼可複用應該始終是我們的目标之一。
下一個改變就是提取出從對象貓到貓名字的轉換(或者映射)。這個變化對以後更有意義,現在隻要相信我就好了:
const getname = cat => cat.name
kittens.push(getname(cats[i]))
我本打算先介紹一下filter和map的,但轉念一想,還是直接展示引入它們之後的代碼多好了解,更能讓你體會到代碼可讀性的巨大變化:
const kittens =
cats.filter(iskitten)
.map(getname)
還要注意,我們已經消除了 kittens.push(...)。不再有可變的狀态,也不再有var!
使用const的代碼有如魔鬼般性感(超過了var和let)。
這裡說明下,我們自始至終都可以使用const,因為const并不會使對象本身不可變(這個咱們下次再說)。别急,這裡隻是在講一個範例,是以先放我一馬!
重構的最後一步就是把過濾和映射方法也提取到一個函數裡(為了複用嘛,你懂的):
整合一起就是這樣:
const getkittennames = cats =>
const kittens = getkittennames(cats)
你會如何進一步分解這些函數?仔細想想“小于”或者name屬性、map或filter,說不定能有額外的收獲。你還可以研究下函數的複合,收益更大。
作者:camiler
來源:51cto