反思JavaScript: 通過函數代替break

原文:https://hackernoon.com/rethinking-javascript-break-is-the-goto-of-loops-51b27b1c85f8#.k2oyppp5i

在我的上一篇文章 Death of the for Loop中,我試圖去說服你放棄使用for 循環改用函數式的解決方案。反過來,你提出了一個很好的問題,那麼for循環中break怎麼辦?

break 會相當於循環中的GOTO,我們應該避免使用。

break 應該像GOTO一樣被廢棄。

你可能會想“算了吧Joel,你這只是聳人聽聞,break怎麼可能會像GOTO一樣?”

// bad code. no copy paste.
outer:
  for (var i in outerList) {
inner:
    for (var j in innerList) {
      break outer;
    }
  }

我可以提供標記作爲證明。在其他語言中,標記和GOTO是相互對應的。在JavaScript,標記與breakcontinue也是相互對應的。因爲breakcontinue來自於相同標記組,這也導致了它們和GOTO很像。

JavaScript標籤,break和continue是GOTO和非結構化編程時代的遺留

xkcd

“但是如果它沒有傷害任何人,那麼我們爲什麼不把它留下語法中,而我們可以選擇其他的方案?”

我們爲什麼限制我們如何編寫軟件?

這個聽上去有些違背直覺,但是限制是一個好事。限制我們使用GOTO就是一個很好的例子。我們也很歡迎限制我們的“use strict”,甚至批評不使用它的人。

“limitations can make things better. A lot better. “— Charles Scalfani

限制(規則)可以使我們寫出更好的代碼。

爲什麼編程需要限制
_限制使藝術,設計,生活更美好._medium.com

我們對於break的選擇是什麼?

我不是要做一個虛有其表的事情,但是也沒有一個方案可以適合所有的情況。 這是一個完全不同的編程方式。 一個完全不同的思考方式。函數式編程的思想。

有一個好消息是,有很多的庫和工具可以幫助我們,例如Lodash, Ramda, lazy.js, 遞歸等等

我們將從一個簡單的cats集合和一個isKitten函數開始,這些將在下面所有的例子中被用到。

const cats = [
  { name: 'Mojo',    months: 84 },
  { name: 'Mao-Mao', months: 34 },
  { name: 'Waffles', months: 4 },
  { name: 'Pickles', months: 6 }
]
const isKitten = cat => cat.months < 7

讓我們從一個我們熟悉的for循環的例子開始。它會遍歷我們的cats,然後當找到第一隻小貓的時候退出循環。

var firstKitten
for (var i = 0; i < cats.length; i++) {
  if (isKitten(cats[i])) {
    firstKitten = cats[i]
    break
  }
}

現在,讓我們和lodash中一個相同作用的例子做比較。

const firstKitten = _.find(cats, isKitten)

這個例子相當的簡單。接下來讓我們嘗試一些邊緣情況吧。現在我們改爲遍歷cat集合,然後選出前5只小貓,然後退出循環。

var first5Kittens = []
// old-school edge case kitty loop
for (var i = 0; i < cats.length; i++) {
  if (isKitten(cats[i])) {
    first5Kittens.push(cats[i])
    if (first4Kittens.length >= 5) {
      break
    }
  }
}

簡單的方式

lodash是一個很好的庫也可以坐很多的事情,但是有時候你需要一些其他更加專業的工具。這裏我們介紹一個新朋友, lazy.js. “像Underscore,但是更加偷懶”。但是偷懶就是我們想要的.

const result = Lazy(cats)
  .filter(isKitten)
  .take(5)

困難的方法

庫都是有趣的,但是有時候真正有趣的是從頭開始創造東西

所以我們可以創建一個通用的函數,讓它可以像filter一樣使用也可以增加限制的功能。

第一步就是把我們上面寫的邊緣情況的for循環封裝在一個函數中。

接下來,讓我們是這個函數更加通用並且遍歷所有cat具體的內容。使用limit來代替5,predicate來代替isKittenlist來代替cats。然後把這些作爲函數的參數。

現在我們有了一個可用的且可重複的takeFirst函數,這個可以讓我們完全不用去關心我們cat的邏輯實現!

我們的函數現在依舊還是一個純函數。也就是說函數的輸出只和輸入的參數有關。如果傳入相同的參數,一定會得到相同的結果。

現在我們已經還是有那個骯髒的for循環,所以讓我們繼續重構。下一步就是把inewList放入參數列表。

limit變爲0的時候 (limit會在遞歸過程中減少)或者是遍歷完了列表,我們希望可以退出遞歸(isDone)。

如果遞歸還在進行,我們將會覈對是否有符合我們的過濾條件predicate的值。如果當前值符合過濾條件,我們會調用takeFirst,減少limit並把當前值保存在我們的newList中,否者,移動到列表的下一個值。

如果你還沒有看過Rethinking JavaScript: The if statement,它會解釋這個用三元表達式代替if‘的最後一步。

Rethinking JavaScript: The if statement
_Thinking functionally has opened my mind about programming._medium.com

現在我們像下面這樣調用我們的新方法:

const first5Kittens = takeFirst(5, isKitten, cats)

爲了兼容更多的情況,我們可以柯里化takeFirst,然後使用它去創建一些其他的函數(關於柯里化的介紹在另一篇文章中)

const first5 = takeFirst(5)
const getFirst5Kittens = first5(isKitten)
const first5Kittens = getFirst5Kittens(cats)

總結

現在有很多優秀的庫例如 lodash, ramda和lazy.js供我們使用。但是如果我們足夠大膽,也可以使用遞歸來創建我們自己的函方法。

我必須要警告雖然takeFirst看上去很酷 但是使用遞歸是有得也有失的. 遞歸在Javascript中是很危險的,它很容易就會導致超出最大調用堆棧大小的報錯。

我將會在我的下一篇文章中重寫JavaScript的遞歸。敬請關注。

我知道這只是一件小事,但是當我收到來自Medium和Twitter (@joelnet)的follow通知時會使我很開心。當然,如果你覺得我實在胡說八道,你也可以在下面的討論區告訴我。

Cheers!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章