沒有循環的JavaScript

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

 

「……循環是個不可避免的結構,並且很差複用,同時循環還很難加入其餘操做中。更麻煩的是,使用循環就意味着在每個新的迭代中有更多變化須要響應」——Luis Atencio數組

 

循環瀏覽器

 

相似循環同樣的控制結構會讓代碼變得複雜。但目前並無什麼證據能證實它。如今讓咱們看看JavaScript中的循環是如何工做的。app

 

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

 

如今咱們有了一個數組,來用oodify處理它。當咱們使用while時,循環應該這樣寫:性能

 

請注意,爲了知道咱們所在的位置,我用了計數器i。首先將計數器初始化清零,以後在每次循環中加1計數。同時,咱們還要比較i和數組長度,這樣才能知道何時中止循環。JavaScript提供另一個和它差很少,並且更簡單的寫法:for循環。用for循環能夠這樣寫:ui

 

for循環是個頗有用的結構,由於它能夠將計數器的邏輯都包含在頂部,這是個很不錯的改進。當咱們使用while循環時,很容易就忘了寫i的加1計數,而後形成無限循環。如今咱們來看看這段代碼的做用。咱們試圖對數組中的每一個元素調用oodlify(),而後將結果存入新的數組。事實上咱們不太想本身操做計數器。3d

 

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

 

這個方法看上去簡單不少。能夠注意到計數器以及比較數組長度的過程都不見了。咱們甚至不用本身將元素從數組中取出。for…of循環幹了全部髒活累活。若是咱們用for…of循環替代全部的for循環,就會取得很大進步。如今咱們已經讓代碼變得更簡單,但咱們的目標不止如此。blog

 

映射(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將數組減至一個元素。那若是咱們想從數組中提取某些元素該怎麼辦?爲了繼續研究,來稍微擴充一下咱們以前的數據:

 

如今面前有兩個目標:

  • 找到全部女英雄;

  • 找到那些力量值大於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函數解決了以前查找特定元素的問題,如今有一個新的疑問:我怎麼知道是找到特定的元素就結束仍是遍歷整個數組。然而這並非咱們要關心的內容!

 

小結:

 

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

  • 剔除循環控制結構,加強代碼可讀性;

  • 用現有的方法來概括例子中的模式;

  • 明確咱們到底要對數組中的元素作什麼操做。

 

在每一個例子中,咱們都用小而純粹的函數將問題分解。真正重要的就是這四種模式(也有其餘方法,但我推薦這四種),用它們你幾乎能夠淘汰JavaScript中全部的循環了。這是由於幾乎JavaScript中全部的循環都是來處理數組,或建立數組的。在減小循環的過程當中,咱們不但減小了代碼的複雜性,同時也加強了代碼的可維護性。

相關文章
相關標籤/搜索