如何優雅地改善程式中for迴圈

Andraw-lin發表於2018-10-17

眾所周知,在我們的日常編碼裡,對於一些資料的迭代以及遍歷,首先想到的是for迴圈

這沒毛病,for迴圈編寫語法很簡單,只需要知道資料列表的長度即可進行遍歷取值,舉個例子:

for迴圈

有點程式設計基礎的童鞋,都可以很快想到上面的編寫方法。那不知道大家是否會跟我一樣對於寫for迴圈時,功能是沒毛病,但編寫起來總會覺得很不優雅,例如還要定義對應的i變數(拿上述舉例)以及還要自主控制i變數的變化,這樣無疑會變得繁瑣起來,也會讓我們編寫程式碼效率有所下降

好了,那我們有沒有一些優雅的方法處理上述案例呢?答案是肯定的,這就回到這篇文章的主題:如何優雅地改善for迴圈?

ForEach

相信學過ES6的童鞋,很快就會想到這種方法,下面我就拿上面的例子改造一下:

forEach遍歷

這種方式,看起來優雅了許多,省去了不必要的佇列長度對比變數的定義和控制以及佇列長度判斷

到這裡,ForEach是不是就是最好的優雅方案?有沒有存在問題?,相信一部分童鞋未必能答出來,那我就不賣關子了。如果我們遇到一種情況,就是對列表遍歷處理後,需要返回一個處理後的陣列,那我們繼續使用forEach是否能夠達到我們的目的呢?我們也許會有以下做法:

forEach全域性汙染

可以看到,第一種處理是希望處理完後直接賦值給一個變數,然而forEach迴圈返回的是undefined,第二種處理雖然最終的確能夠達到我們目標,拿到處理完的資料,卻導致了一個問題--全域性變數的汙染。當然也可以在外部定一個陣列,然後push處理後的資料到新陣列來實現我們的目標,但整體看起來依然不是很優雅。

那有木有一種方法能夠返回一個新陣列,並且又不會影響到原來的資料呢?答案是肯定的,這就要提到強大的Functional Programing(簡稱FP)

Functional Programing

FP是什麼?又是用來幹什麼的?

Functional programmings(FP) means coding in functions

這是比較簡潔和官方的說法,那是什麼意思?簡單地說,就是,用函式的方式處理問題,今天就會用到它的其中一個核心思想:純函式(對於相同的輸入,永遠得到相同的輸出,而且不會有任何的副作用)

看到這裡是不是有點頭緒了?。好啦,回到正題,那在js裡面有那些方法是純函式,而且又能優雅地替代for迴圈?相信童鞋們都會想到了map()

Map()

Map方法會對原始陣列中的每個元素進行遍歷一次,並呼叫callback,最終返回一個新的陣列而不會影響到原始陣列。

我們現在就拿上述forEach存在問題的栗子進行改造一下:

Map迴圈

好明顯,曾經的多行迴圈程式碼只需要一行就可以完成,看著也優雅了好多,而且返回一個新的處理後的素組以及不會對原有的陣列產生汙染

Filter()

Filter方法,簡單滴說就是對陣列進行篩選或過濾,然後返回一個新的陣列,並不會對原始陣列產生任何的影響

看到這裡,我們又要回到for迴圈那個栗子,使用Filter方法是否可以讓程式碼優雅起來?現在就來動手看看:

filter迴圈

明顯,使用Filter方法只能達到了第一步目標就是優雅了for迴圈裡的條件語句,那該怎麼辦?

我們會發現,Filter方法是會返回一個新陣列,那我們就可以聯絡到了Map方法,兩者結合會產生什麼火花?

filter與map結合

至此,是否會覺得看起來舒服好多,而且也通俗易懂,而這也是最終優雅方案。那麼問題來了,什麼時候該用forEach,什麼時候又該用map呢?

forEach比較適用於不改變原始陣列資料,僅僅拿陣列資料進行做一些事情,而map則適用於改變資料值並且返回一個新陣列,當然不同情景又不同的做法,這僅僅只是作為一個參考

Extension

除了上述提到的常用情景外,我們還會遇到很多其他地方可以繼續優雅for迴圈,這就不得不提some方法和every方法以及reduce方法(reduce也屬於FP)

  1. Every()和Some()

    現在有一種情景,就是要根據後端返回的一個列表裡費用低於0時就展示提示語。結合上述學過的方法,也許我們會有以下做法:

    for迴圈

    除了上述比較的直接方法外,也許有些童鞋會想到直接使用forEach來進行優雅一下:

    forEach迴圈

    因為forEach處理資料是在callback上,因此使用break語法明顯會導致語法錯誤,因此,就算去掉break,也需要遍歷完所有元素,而主角來了,some和every:

    • some:遍歷陣列元素,只要遇到條件為true則直接返回true,否則直到遍歷最後並返回false

    • every:遍歷陣列元素,只要遇到條件為false則直接返回false,否則直到遍歷最後並返回true

    some和every

    明顯看著程式碼量就簡潔了好多,而且也不失優雅?。

  2. reduce()

    可能有些童鞋對於reduce方法有點陌生,它究竟是用來幹嘛的?應用場景又是什麼?

    reduce() 方法接收一個函式作為累加器(accumulator),陣列中的每個值(從左到右)開始縮減,最終為一個值

    上面為官方的說法,簡單地理解,就是對一個列表進行遍歷,第一次取兩個元素進行處理得到一個新值,然後到下一次迴圈時就拿該新值與第三個元素進行處理,以此類推,最終得到一個值並返回。文字太複雜,直接圖解:

    reduce解析

    是不是清晰很多了,那應用的優雅情景又有哪些?在這裡我就不賣關子了,例如,後端返回一個物件,而我們需要對這個物件進行處理只需要得到指定屬性值,多餘的屬性就過濾掉。日常做法:

    物件遍歷

    可以看到,雙層迴圈讓人看起來的確複雜很多,而且稍微不小心就會很容易出問題。然而使用reduce方法就可以優雅起來

    reduce遍歷

    看了這個,是不是覺得reduce用了後特別簡潔和優雅。

    如果你覺得它還不夠強大,再看看下面一個栗子,就可以覺得它的厲害(這是額外話題了,大家有空可以研究一下)。我們經常會遇到一種情況就是,後端返回一個巢狀很深的物件,然後前端拿到這個複雜的物件中某個屬性時,難免會需要每一個值都要判斷是否存在,然後直到指定的屬性為止,這就是鏈式取值問題:

    鏈式取值最死板方法

    其實鏈式取值問題在網上有很多處理方案,那如果是最優雅方案,我覺得就是使用reduce方法,那麼使用reduce怎麼編寫呢?直接上圖

    reduce鏈式取值

    至此,對於for迴圈的優雅性,不同場景可以有很多的優雅方案,但我想說的是,原生js本身提供的api其實有很多方法是可以讓我們的程式變得便捷以及簡潔的,只有你多點用用它,就會體會到它的強大。哈哈,如果上文如果有不妥之處,歡迎各位大佬指點一下?

Last

最後,看完上文,相信大家對for迴圈有一定的認識了(我猜的),那就用一道常見的面試題作為結尾來讓大家回去思考一下用什麼方法最好吧:

實現一個方法,計算出一個字串中每個字元出現的次數?

方法我就不寫了,讓大家來一場頭腦風暴吧?

相關文章