在JavaScript函數語言程式設計裡使用Map和Reduce方法

2016-04-13    分類:WEB開發、程式設計開發、首頁精華1人評論發表於2016-04-13

本文由碼農網 – 唐李川原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃

所有人都談論道workflows支援ECMAScript6裡出現的令人吃驚的新特性,因此我們很容易忘掉ECMAScript5帶給我們一些很棒的工具方法來支援在JavaScript裡進行函式程式設計,這些工具方法我們現在可以使用了。在這些函式方法裡主要的是基於JavaScript 陣列物件的map()方法和reduce()方法。

如果你如今還沒有使用map()reduce()方法,那麼現在是時候開始使用了。如今絕大部分的JavaScript開發平臺都與生俱來的支援ECMAScript5。使用Map方法和reduce方法可以使你的程式碼更加簡潔和更容易閱讀和更容易維護,而且把你帶到朝向更簡潔的功能開發的路上。

效能:一個警告

當然,當現實狀況需要保持提高效能時,你的程式碼的易讀性和易維護性不得不在兩者之間保持平衡。如今的瀏覽器使用更笨重的傳統技術例如for迴圈來執行的更有效率。

我寫程式碼的方式通常是把可讀性和可維護性放在編寫程式碼的第一位,然後如果我發現在現實情況裡程式碼執行出現了問題,再去為了提高效能而去優化程式碼。過早的優化程式碼是很困難的,而且這種優化會導致後面編寫程式碼很困難。

值得考慮的是在JavaScript引擎裡使用諸如map()和reduce()這樣的方法可以更好地改善程式碼的效能,而不是指望將來瀏覽器能為了改善效能而做出優化。除非我在一個效能問題上碰壁,要不然我更喜歡開心地寫程式碼,然而以防我需要它們我卻隨時準備著為保持程式碼的效能而做出調整,儘管這樣做使我的程式碼減少了吸引力。

使用Map方法

對映是一個基本的函數語言程式設計技術,它對一個陣列中的所有元素和建立的具有相同長度並有著轉換內容的其他陣列起作用。

為了使剛才的說法更加具體一點,我想出了一個簡單使用示例。例如,想象一下你有一個陣列,陣列裡有字元資料,而且你需要把它們轉換進另一個陣列,這個陣列裡包含每一個字元資料的長度。(我知道,那沒有複雜到火箭科學那種程度,這是你編寫複雜的應用經常不得不去做的事情,但是理解它在一個簡單示例例如這個裡的工作原理將有助於你使用它,在這種情況下,你可以在你的程式碼裡給它新增真實的資料值)。

你也許知道剛才我描述的在一個陣列上使用for迴圈如何做。它可能看起來像這樣:

var animals = ["cat","dog","fish"];
var lengths = [];
var item;
var count;
var loops = animals.length;
for (count = 0; count < loops; count++){
  item = animals[count];
  lengths.push(item.length);
}
console.log(lengths); //[3, 3, 4]

所有我們需要做的事情是定義少量的變數:一個命名為animals的陣列包含了我們需要的字元資料,一個命名為lengths的空陣列將來用來包含我們運算元組資料輸出的長度,和一個叫做item的變數,這個變數用來臨時儲存每一次迴圈遍歷該陣列時我們需要操作的陣列項。我們定義了一個for迴圈,這個for迴圈有個內部變數和一個迴圈變數來初始化我們的for迴圈。然後我們迴圈迭代每一個項,直到迭代的陣列長度等於animals陣列的長度。每一次迴圈我們都計算迭代項的字元長度,然後把它儲存到lengths陣列裡。

注意:需要討論的是我們可以把上面的程式碼寫得更簡潔些,不需要那個item變數,而直接把animals[count]的長度放到lengths陣列裡,這樣就不需要中間的轉換過程了。這樣做可以幫我們省一點點程式碼空間,但是它也使得我們的程式碼有點不易閱讀,即使是這個很簡單的例子也一樣。相同地,為了使程式碼更有效率而少些直白,我們可能會使用已知的animals陣列長度來通過new Array(animals.length)的方式來初始化我們的lengths陣列,然後通過索引而不是使用push方法來插入選項資料長度。這取決於你在現實中準備如何去使用這些程式碼。

這種實現方式沒有任何的技術錯誤。它在任何標準的JavaScript引擎裡都可以用,並且它能很好的完成工作。一旦你知道怎麼使用map()方法了,通過這種方法來實現就顯得有些笨拙了。

讓我展示給你看使用map()方法是如何實現上面的程式碼的:

var animals = ["cat","dog","fish"];
var lengths = animals.map(function(animal) {
  return animal.length;
});
console.log(lengths); //[3, 3, 4]

在這個小例子裡,我們首先再次為我們的animal型別的陣列建立一個animals變數。然而,其他我們需要宣告的變數就是lengths變數了,然後我們直接分配它的值到對映一個匿名行內函數到animals陣列的每一個元素上的結果中。

那個匿名函式執行在每一個animal 上的的操作,然後返回該animal的字元長度。這樣做的結果是,lengths變成了具有與原始animals陣列有相同長度的陣列,並且該陣列包含了每一個字元的長度。

這種方式需要注意的一些事情。首先,它比最初編寫的程式碼更簡潔。其次,我們僅需要申明更少的變數。更少的變數意味著在全域性名稱空間裡雜音更少,並且如果相同程式碼的其他部分使用了相同的變數名就會減少碰撞的機會。最後,我們的變數不可能從頭到腳都會改變它們的值。隨著你深入函數語言程式設計,你將會欣賞使用常量和不變的變數的優雅的能力,現在開始使用並不算早。

這種實現方式的另一個優點是,我們可以通過在整個程式碼編寫過程中通過將程式碼放進一個個命名的函式裡而簡化程式碼而有機會提升自己編寫程式碼的多功能性。匿名的行內函數看著有些亂,並且使得重複使用程式碼更困難。我們可以定義一個命名為getLength()的函式,並且像下面的方式來使用:

var animals = ["cat","dog","fish"];
function getLength(word) {
  return word.length;
}
console.log(animals.map(getLength)); //[3, 3, 4]

看看上面的程式碼看上去多簡潔啊?只是把你處理資料的部分對映一下就使你的程式碼達到一個全新的功能水平。

什麼是函子?

有趣的一點是,通過在陣列物件裡新增對映,ECMAScript5把基本的陣列型別變成了一個完整的函子,這使得函數語言程式設計對我們來說更加的容易。

根據傳統的函式程式設計定義,一個函子需要滿足三個條件:

  • 1.它儲存著一組值。
  • 2.它實現了一個map函式來操作每一個元素。
  • 3.它的map函式返回一個具有同樣大小的函子。

這個將會在你下一次的JavaScript聚會上被翻來覆去的討論。

如果你想了解更多關於函子的資訊,你可以看看Mattias Petter Johansson錄製的關於這方面的視訊

使用Reduce方法

Reduce()方法在ECMAScript5裡也是新增的,而且它和map()方法很類似,除了不產生其他的函子,reduce()方法產生的一個結果是它可能是任何型別。例如,設想一下,如果你想得到我們animals陣列裡的所有字元長度都分別作為一個數字然後相加的結果。你也許會像下面這樣做:

var animals = ["cat","dog","fish"];
var total = 0;
var item;
for (var count = 0, loops = animals.length; count < loops; count++){
  item = animals[count];
  total += item.length;
}
console.log(total); //10

在我們定義我們的初始陣列之後,我們為執行總計定義了一個total變數,並把它的初始值設為0。我們也定義一個變數item來儲存每一次for迴圈迭代animals陣列的迭代值,並且再定義一個count變數作為一個迴圈計數器,並且用這兩個變數來初始化我們的迭代。然後我們執行for迴圈來迭代animals陣列裡的所有字元資料,每次迭代都會把迭代的結果儲存到item變數。最終我們把每一次迭代到的item的長度加到我們的total變數裡就可以了。

這種實現方式也沒有任何的技術錯誤。我們從定義一個陣列開始,然後得到一個結果值就結束了。但是如果我們使用了reduce()方法,我們可以使上面的程式碼更加簡單明瞭:

var animals = ["cat","dog","fish"];
var total = animals.reduce(function(sum, word) {
  return sum + word.length;
}, 0);
console.log(total);

這裡發生變化的是,我們定義了一個名為total的新變數,並且把執行animals陣列物件的reduce方法的返回值分配給它,在reduce方法裡有兩個引數:一個匿名內聯function方法,和初始化total的值為0。對於陣列中的每一個項reduce()方法都會執行,它在陣列的那一項上執行這個function函式,並且把它新增到執行總計,然後再進行下一次迭代。這裡我們的內聯function方法有兩個引數:執行總計,和當前程式正在處理的從陣列中獲取的字元。這個function函式把當前total的值新增到當前word的長度上。

注意的是:我們設定reduce()方法的第二個引數值為0,這樣做確定了total變數包含的是一個數值。如果沒有第二個引數reduce方法仍然可以執行,但是執行的結果將不是你期望的那樣。(你可以試試並且可以看看當執行總計結束後你是否能推測出JavaScript所使用的程式設計邏輯。)

那看上去似乎比它需要做的更有一點複雜,因為當呼叫reduce()方法時需要在一個內聯function裡綜合的定義。我們再那麼做一次,但是首先讓我們定義一個命名的function函式,而不再使用匿名行內函數:

var animals = ["cat","dog","fish"];
var addLength = function(sum, word) {
  return sum + word.length;
};
var total = animals.reduce(addLength, 0);
console.log(total);

這個程式碼稍微有點長,但是程式碼有點長並不總是壞事。這樣寫你應該看到它使程式碼更加清晰些,只是在reduce()方法裡發生了一點變化。

該程式裡reduce()方法有兩個引數:一個function函式,用來呼叫陣列裡的每一個元素,和一個為total變數設定的執行總計初始值。在這個程式碼示例中,我們放入了一個名為addLength的新function和為執行總計變數賦初始值為0。在上一行程式碼中,我們定義了一個名為addLength的function函式,這個函式也需要兩個引數:一個當前和一個要處理的字串。

結論

經常使用map()方法和reduce()方法將會為你的程式碼更加簡潔,更加多功能,更加可維護性提供了選擇。同時它們為你使用更多的JavaScript功能函式技術鋪平了道路。

map()方法和reduce()方法僅是被新增進ECMAScript5中新方法中的兩個。從使用它們的今天你將會明白程式碼質量的提高和開發人員滿意度的提升勝過任何在效能上的臨時影響。在判斷map()方法和reduce()方法是否適合你的應用之前,試著用功能函式技術來開發和度量一下它在你現實世界裡的影響,然後再判斷是否去用。

譯文連結:http://www.codeceo.com/article/javascript-map-reduce-method.html
英文原文:Using Map and Reduce in Functional JavaScript
翻譯作者:碼農網 – 唐李川
轉載必須在正文中標註並保留原文連結、譯文連結和譯者等資訊。]

相關文章