[譯] 沒有循環的 JavaScript

BAFNikole 7年前發布 | 9K 次閱讀 JavaScript開發 JavaScript

有些文章中提到過,縮進(并不能特別準確的)說明了代碼的復雜程度。我們想要的是簡單的JavaScript。之所以層層縮進,是因為我們用抽象的方式解決問題。但要選用什么抽象方法呢?截止目前,我們沒有在特定環境中說明該使用什么樣的方法。本文將關注如何在擺脫循環的情況下使用數組。最終的結果當然是更簡單可讀的代碼。

“……循環是個不可避免的結構,而且不好復用,同時循環還很難加入其他操作中。更麻煩的是,使用循環就意味著在每一個新的迭代中有更多變化需要響應”——Luis Atencio

循環

類似循環一樣的控制結構會讓代碼變得復雜。但目前并沒有什么證據能證明它。現在讓我們看看JavaScript中的循環是如何工作的。

在JavaScript中,我們至少有4到5種循環的方法。最基本的要數 while 循環。開始之前我們先寫一個示例函數和數組方便我們說明:

現在我們有了一個數組,來用 oodify 處理它。當我們使用 while 時,循環應該這樣寫:

請注意,為了知道我們所在的位置,我用了計數器 i 。首先將計數器初始化清零,之后在每次循環中加1計數。同時,我們還要比較i和數組長度,這樣才能知道什么時候停止循環。JavaScript提供另外一個和它差不多,而且更簡單的寫法: for 循環。用 for 循環可以這樣寫:

for 循環是個很有用的結構,因為它可以將計數器的邏輯都包含在頂部,這是個很不錯的改進。當我們使用 while 循環時,很容易就忘了寫 i 的加1計數,然后造成無限循環。現在我們來看看這段代碼的作用。我們試圖對數組中的每個元素調用 oodlify() ,然后將結果存入新的數組。事實上我們不太想自己操作計數器。

這種對每一個數組元素做處理的方式十分常見。所以在ES2015中,提供了一個可以不用在意計數器的新的循環結構: for…of 循環。每一輪循環它都會將數組中的下一個元素傳給你。它看上去是這樣的:

這個方法看上去簡單很多。可以注意到計數器以及比較數組長度的過程都不見了。我們甚至不用自己將元素從數組中取出。 for…of 循環干了所有臟活累活。如果我們用 for…of 循環替代所有的for循環,就會取得很大進步。現在我們已經讓代碼變得更簡單,但我們的目標不止如此。

映射(Mapping)

for…of 循環要比 for 循環簡單的多,但還是需要一些手動配置。首先需要初始化 output 數組,還要在每層循環中調用 push() 函數。如果能解決代碼中一些現有的問題,還可以將代碼變得更清晰明了,其存在的問題是:

如果有兩個數組都需要調用 oodlify 怎么辦?

首先想到的應該是對兩個數組都用循環:

這固然有用。而且好處大于壞處。但這個方法重復使用了太多次——不是特別清爽。現在我們要去除一些重復來將它重構,先寫一個函數:

看上去是不是好些了,但如果我們還有想要的函數怎么辦?

這種情況下 oodlifyArray() 函數就幫不上什么忙了。如果我們創建一個 izzlifyArrya() 函數,這就又走了那個不斷重復的老路。不管怎樣,先試試,我們好看看到底是什么效果:

可以看出這兩個函數功能驚人的相似。那如果我們可以將其中的模式抽象出來會怎樣?事實上,我們想要的是:對于給出的數組和函數,將數組中的每個元素映射到新的數組中。然后把函數作用在每個元素上。我們把這種情況稱為模式映射。數組的映射函數長這樣:

這個方法還是沒有完全擺脫循環。如果我們想要擺脫循環,那就需要寫一個遞歸的版本:

遞歸的方法好像挺高級。只需要兩行代碼,沒什么縮進。但一般來說,我們不怎么用遞歸的方法,因為它在老版本瀏覽器上性能不怎么樣。而且事實上,我們并不需要自己去寫映射函數(除非你想這么做)。映射函數實際上非常常見,所以JavaScript為我們提供了一個創建映射的方法。用映射方法,代碼看上去是這樣的:

注意到這種寫法完全沒有縮進。完全沒有循環。事實上,其內部某些地方確實存在循環,但這完全不是我們需要關心的。這下代碼看上去就非常的簡單了。

那為什么這樣寫看上去特別簡單呢?這個問題似乎特別蠢,但請仔細想想。是因為它特別短嗎?答案是否定的。僅僅因為代碼量少并不代表它簡單。它看起來簡單是因為我們把他們分開了。有兩個函數來處理字符串: oofligy 和 izzlify 。這些函數與數組或循環無關。另一個函數 map 會處理數組。但 map 不會管數組中的數據是什么類型,或你想用這些數據干什么。它只是在調用我們傳給他的函數。和把所有東西混在一塊不一樣,我們將字符串的處理過程和數組的處理分開。這就是代碼簡單的原因。

精簡(Reducing)

現在 map 這個函數非常便利,但它沒辦法覆蓋我們需要的所有循環。它只在你想要創建一個和輸入一樣長的數組才有用。那如果我們想增加數組元素數量怎么辦?或是想要在列表中找出最短的字符串。還有些時候我們想處理一個數組或將其元素減至一個。

現在來看個例子。假如我們有一個英雄對象的數組:

我們想找到最強壯的英雄。使用 for 循環,過程是這樣:

代碼看上去不錯,它將所有事情考慮了進去。當我們開始循環的時候,始終可以從strongest中獲取當前循環中最強壯的英雄。那新的問題來了,假設我們想知道所有英雄加起來有多強。

兩個例子中,我們在開始循環之前都先初始化了一個變量。然后每一次循環中,都從數組中去取出一個值,然后更新這個變量。為了使循環更加簡潔,我們從循環中提取因子然后使用函數。同時,我們將重新命名一些變量。

這樣寫的話,兩個循環看上去就非常相近。兩者的區別僅僅存在于函數名和初始值上。兩個方法都將數組的元素減至一個。因而我們可以創建一個 reduce 函數來繼續壓縮這個模式。

JavaScript在reduce上為數組提供一種如 map 一樣內嵌的方法。這樣我們就不用自己寫相關的方法了。使用內嵌的方法,代碼應該是這樣的:

如果大家有仔細閱讀本文,你應該發現這段代碼并不是最短的。使用數組內嵌的方法,我們也就減少了一行代碼。但我們的目標是盡量減少函數的復雜性,而不是追求更少的代碼量。那這樣寫到底有沒有減少復雜性呢?答案是肯定的。將代碼從獨立處理元素的過程中分離,令其單獨處理循環。這樣代碼就減少了一些復雜性。

過濾(Filtering)

我們首先使用 map 可以對數組中的每個元素進行操作。同時我們用 reduce 將數組減至一個元素。那如果我們想從數組中提取某些元素該怎么辦?為了繼續研究,來稍微擴充一下我們之前的數據:

現在面前有兩個目標:

  1. 找到所有女英雄;
  2. 找到那些力量值大于500的英雄

先用老方法 for 循環,可以這樣寫:

這段代碼挺不錯的,它考慮到了所有的內容。但其中絕對有一些重復的模式。事實上,唯一改變的就是那個 if 條件申明。那現在將 if 拿出來單獨作為函數。

這個的函數只會返回 true 或 false ,它有時被稱為 predicate 。我們使用 predicate 來決定到底要不要保留heroes中的元素。

這樣的寫法讓代碼變得更長了。如果我們將 predicate 函數提取出來,提取后的版本變得清晰無比。我們用提取的部分創建函數。

和 map , reduce 一樣,JavaScript為我們提供的也是數組方法。所以我們不用來自己多寫什么(除非你想要這樣做)。使用數組方法,代碼變成了:

為什么這樣比使用 for 循環強太多?思考一下我們是如何在例子中使用的。我們最初的問題是如何找出符合條件的英雄。當我們用 filter 函數解決了這個問題后,剩下的工作輕松無比。我們寫了一個簡單的函數,用它告訴 filter 函數哪些元素需要保留。最后我們寫了一個非常簡單的 predicate 函數,就不用考慮數組或變量了。

與其他方法相比,使用 filter 能傳遞更多信息,并且使用了更少的空間。完全沒必要熟悉所有循環后來實現過濾。只需要寫一個方法調用即可。

查找(Finding)

Filtering用起來很方便,但如果我們只想找一位英雄呢?假設我們要找到Black Widow。當使用 filter 函數:

這樣寫效率不高。 filter 需要查看數組中的每個元素。但我們知道只有一個Black Widow,完全可以在找到一個Black Widow后結束查找。 predicate 函數的用法是非常靈活的。我們可以來寫一個 find 函數以返回匹配到的第一個元素。

和之前一樣,JavaScript可以包辦全部,我們不用自己創建什么復雜函數:

最終我們用較少的文字表達了更多內容。用 find 函數解決了之前查找特定元素的問題,現在有一個新的疑問:我怎么知道是找到特定的元素就結束還是遍歷整個數組。然而這并不是我們要關心的內容!

小結:

從這些迭代函數中不難看出抽象思維的價值。假設我們用內嵌數組的方法處理一切問題。在每個案例中我們都完成了三件事:

  1. 剔除循環控制結構,增強代碼可讀性;
  2. 用現有的方法來歸納例子中的模式;
  3. 明確我們到底要對數組中的元素做什么操作。

在每個例子中,我們都用小而純粹的函數將問題分解。真正重要的就是這四種模式(也有其他方法,但我推薦這四種),用它們你幾乎可以淘汰JavaScript中所有的循環了。這是因為幾乎JavaScript中所有的循環都是來處理數組,或創建數組的。在減少循環的過程中,我們不但減少了代碼的復雜性,同時也增強了代碼的可維護性。

 

 

來自:https://github.com/Findow-team/Blog/issues/16

 

 本文由用戶 BAFNikole 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!