在JavaScript函數式編程裡使用Map和Reduce方法

2021-02-19 尚學堂

譯文連結:http://www.codeceo.com/article/javascript-map-reduce-method.html
英文原文:Using Map and Reduce in Functional JavaScript   唐李川

所有人都談論道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函數返回一個具有同樣大小的函子。

如果你想了解更多關於函子的信息,你可以看看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()方法是否適合你的應用之前,試著用功能函數技術來開發和度量一下它在你現實世界裡的影響,然後再判斷是否去用。

尚學堂:每天推送IT新技術文章,跟著我們擴展技術視野吧


點個讚再走唄!

即刻起關注尚學堂,發送「要課程」,

IT課程免費送!

相關焦點

  • 詳解Python函數式編程之map、reduce、filter
    map()、reduce()、filter()是Python中很常用的幾個函數,也是Python支持函數式編程的重要體現。
  • 使用JavaScript對象數組進行函數式編程
    我們著眼於使用map,filter和reduce來操縱對象數組,使用從函數式編程中借用的技術。數據操作是任何JavaScript應用程式中的常見任務。在這篇文章中,我們將打破使用的幾個例子map,reduce和filter操作對象的數組。通過這些例子,我們將學習這些方法的強大程度,同時了解它們與函數式編程的關係。功能編程:好的部分函數式編程有許多概念超出了我們要實現的範圍。在本文中,我們將討論一些絕對的基礎知識。
  • JavaScript 關於map,reduce函數的一些思考
    map函數與reduce函數屬於函數式編程,通俗點說,可以把函數作為參數或返回值自由傳遞。
  • 走近 (javascript, 函數式)
    其中,我們把一類相關的數據和命令封裝在一起,形成了類和對象,形成了面向對象的編程方式。函數式編程卻屬於聲明式的編程方式,這種範式會描述一系列的操作,而不去暴露它們是如何實現的,以及數據是如何從中間穿過。在這裡,「動詞」是第一詞彙,我們更加注重於描述不同動作之間的組合和順序。比如,我們需要將一個數組的每個數平方以後,找出其中的奇數,並生成新的數組。
  • python高階函數:map、filter、reduce的替代品
    根據單詞長度,使用sorted函數對一個列表進行排序。其中將len函數傳給key參數,具體示例如下:這裡需要特別提示一下,任何單參數函數都能作為key參數的值。在函數式編程中,大家最熟悉的高階函數主要有map函數、filter函數、reduce函數和apply函數。
  • Python入門基礎之map()、reduce()函數使用
    1、 map()函數map()是 python 內置的高階函數,它接收一個函數 f 和一個 list,並通過把函數 f 依次作用在 list 的每個元素上,得到一個新的object並返回。(python2返回列表,Python3返回迭代對象)map()的使用方法形如map(f(x),Itera),它有兩個參數,第一個參數為某個函數,第二個為可迭代對象。
  • 一文帶你了解什麼是JavaScript 函數式編程?
    函數式編程在前端已經成為了一個非常熱門的話題。在最近幾年裡,我們看到非常多的應用程式代碼庫裡大量使用著函數式編程思想。本文將略去那些晦澀難懂的概念介紹,重點展示在 JavaScript 中到底什麼是函數式的代碼、聲明式與命令式代碼的區別、以及常見的函數式模型都有哪些?
  • 函數式編程
    函數式編程的幾個技術map & reduce :這個技術不用多說了,函數式編程最常見的技術就是對一個集合做Map和Reduce操作。這比起過程式的語言來說,在代碼上要更容易閱讀。Map & Reduce在函數式編程中,我們不應該用循環迭代的方式,我們應該用更為高級的方法,如下所示的Python代碼name_len = map(len, ["hao", "chen", "coolshell"])print name_len# 輸出 [3, 4, 9]
  • Python基礎應用:map與reduce
    map()和 reduce() 函數的設計靈感就來源於函數式編程的思想。簡單講,函數式編程的主要思想是儘量將運算過程編寫成一系列的嵌套函數。 map() 和 reduce() 都是Python內置的高階函數。所謂高階函數,即以函數作為輸入參數或輸出結果的函數,map() 和 reduce()的共同特徵是以函數作為傳入參數。
  • [翻譯]淺談JavaScript中的高階函數
    高階函數的採用使得JavaScript適合用來做函數式編程。在JavaScript中,高階函數的使用隨處可見。如果你已經用JavaScript寫過一陣子的代碼,那麼你可能已經在不知情的情況下使用過它了。為了完全理解這個概念,你首先要了解什麼是函數式編程以及頭等函數的概念。
  • Python中的函數式編程
    (英語:functional programming)或稱函數程序設計,又稱泛函編程,是一種編程範型,它將電腦運算視為數學上的函數計算,並且避免使用程序狀態以及易變對象。函數程式語言最重要的基礎是λ演算(lambda calculus)。而且λ演算的函數可以接受函數當作輸入(引數)和輸出(傳出值)。
  • 一文讀懂python的map、reduce函數
    map()方法會將 一個函數 映射到序列的每一個元素上,生成新序列,包含所有函數返回值。也就是說序列裡每一個元素都被當做x變量,放到一個函數f(x)裡,其結果是f(x1)、f(x2)、f(x3).組成的新序列。
  • python3 map、reduce、filter 的區別
    在 Python 中有一種編程模式叫做函數式編程,使用的就是 map、reduce、filter 這樣的高階函數(遍歷序列):接收兩個參數,一個是函數,一個是 Iterable,map 將傳入的函數依次作用到序列的每個元素,並把結果作為新的 Iterator 返回
  • JavaScript 數組方法filter和reduce使用詳解
    前言在ES6新增的數組方法中,包含了多個遍歷方法,其中包含了用於篩選的filter和reduce
  • 【第1679其】函數式編程淺析
    這種熟悉需要一定的成本,所以在很多人看來,函數式編程和可讀性存在下面這張圖這樣的關係[2]:函數式編程與可讀性的關係一些函數式編程中的概念學習成本不高,而且能極大的提高代碼的可讀性,比如 map,filter ,所以大部分人在剛剛開始接觸函數式編程會覺得其還挺好的,但是比如對reduce,compose,transduce,剛開始接觸就會覺得不熟悉,不好理解,也就覺得可讀性低。
  • Python編程技巧:如何用Map, Filter, Reduce代替For循環?
    map、filter 和 reduce 這三種技術可以提供描述迭代原因的函數替代方案,以便避免過多的 for 循環。我之前在 JavaScript 中寫過這些技術的入門文章,但是它們在 Python 中的實現略有不同。
  • 圖解 Map、Reduce 和 Filter 數組方法
    、reduce 和 filter 是三個非常實用的 JavaScript 數組方法,賦予了開發者四兩撥千斤的能力。我們直接進入正題,看看如何使用(並記住)這些超級好用的方法!Array.map()Array.map() 根據傳遞的轉換函數,更新給定數組中的每個值,並返回一個相同長度的新數組。它接受一個回調函數作為參數,用以執行轉換過程。let newArray = oldArray.map((value, index, array) => { ...
  • 聲明式編程和命令式編程的比較
    而使用聲明式編程方法,我們可以用 Array.map 函數,像下面這樣: var numbers = [1,2, map函數所作的事情是將直接遍歷整個數組的過程歸納抽離出來,讓我們專注於描述我們想要的是什麼(what)。注意,我們傳入map的是一個純函數;它不具有任何副作用(不會改變外部狀態),它只是接收一個數字,返回乘以二後的值。 在一些具有函數式編程特徵的語言裡,對於list數據類型的操作,還有一些其他常用的聲明式的函數方法。
  • 10分鐘學會python函數式編程
    在這篇文章裡,你將學會什麼是函數範式以及如何使用Python進行函數式編程。你也將了解列表推導和其它形式的推導。
  • 如何用Map、Filter和Reduce替換Python For循環?
    在Python中,這三種技術易函數的形式存在,而不是Array或String類的方法。這意味著要寫成map(function, my_list)而不是my_array.map(function)。此外,每個技術都需要傳遞函數,該函數會被每個項執行。