所有人都談論道workflows支援ECMAScript6裡出現的令人吃驚的新特性,因此我們很容易忘記ECMAScript5帶給我們一些很棒的工具方法來支援在JavaScript裡進行函數編程,這些工具方法我們現在可以使用了。在這些函數方法裡主要的是基於JavaScript 陣列物件的map()方法和reduce()方法。
如果你如今還沒有使用map()和reduce()方法,那麼現在是時候開始使用了。如今絕大部分的JavaScript開發平台都與生俱來的支援ECMAScript5。使用Map方法和reduce方法可以讓你的程式碼更加簡潔和更容易閱讀和更容易維護,而且把你帶到朝向更簡潔的功能開發的路上。
當然,當現實狀況需要保持提高效能時,你的程式碼的易讀性和易維護性不得不在兩者之間保持平衡。現今的瀏覽器使用較笨重的傳統技術例如for迴圈來執行的更有效率。
我寫程式碼的方式通常是把可讀性和可維護性放在編寫程式碼的第一位,然後如果我發現在現實情況裡程式碼運行出現了問題,再去為了提高效能而去優化程式碼。過早的最佳化程式碼是很困難的,而且這種最佳化會導致後面編寫程式碼很困難。
值得考慮的是在JavaScript引擎裡使用諸如map()和reduce()這樣的方法可以更好地改善程式碼的效能,而不是指望將來瀏覽器能為了改善效能而做出最佳化。除非我在一個效能問題上碰壁,否則我更喜歡開心地寫程式碼,然而以防我需要它們我卻隨時準備著為保持程式碼的效能而做出調整,儘管這樣做使我的程式碼減少了吸引力。
映射是一個基本的函數式程式設計技術,它對一個陣列中的所有元素和創建的具有相同長度並有著轉換內容的其他陣列起作用。
為了讓剛才的說法更具體一點,我想出了一個簡單使用範例。例如,想像你有一個數組,數組裡有字元數據,而且你需要把它們轉換進另一個數組,這個數組裡包含每一個字元資料的長度。 (我知道,那沒有複雜到火箭科學那種程度,這是你編寫複雜的應用經常不得不去做的事情,但是理解它在一個簡單示例例如這個裡的工作原理將有助於你使用它,在這種情況下,你可以在你的程式碼裡給它添加真實的數據值)。
你也許知道剛才我描述的在一個陣列上使用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()方法在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()方法是否適合你的應用之前,試著用功能函數技術來開發和度量一下它在你現實世界裡的影響,然後再判斷是否去用。
以上是如何在JavaScript函數式程式設計裡使用Map和Reduce方法的詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!