原文連接: http://jrsinclair.com/articles/2016/gentle-introduction-to-functional-javascript-arrays/;
原文做者: James Sinclair;javascript
這篇文章是介紹函數式編程的四篇文章中的第二篇。在上一篇文章中,咱們看到如何使用函數使代碼抽象地更簡潔,在這篇文章中咱們將使用這個技術在列表上。html
Part1 組成部分和動機,java
Part2 使用數組和列表,編程
Part3 生成函數的函數,數組
Part4 使用函數式編程的風格,app
會想以前的文章,咱們談論了 DRY 準則。咱們看到用函數綁定一系列重複的操做是頗有用的。可是若是咱們重複同一個函數不少次呢?就像這樣:函數式編程
function addColour(colour) { var rainbowEl = document.getElementById('rainbow'); var div = document.createElement('div'); div.style.paddingTop = '10px'; div.style.backgroundColour = colour; rainbowEl.appendChild(div); } addColour('red'); addColour('orange'); addColour('yellow'); addColour('green'); addColour('blue'); addColour('purple');
在這裏 addColour 這個函數被調用不少次,咱們仍然重複了咱們本身,這是咱們一直想回避的。一種重構的方法是構建一個數組包含這些顏色的列表,而後循環調用 addColour 這個函數。函數
var colours = [ 'red', 'orange', 'yellow', 'green', 'blue', 'purple' ]; for (var i = 0; i < colours.length; i = i + 1) { addColour(colours[i]); }
JavaScript 容許咱們把一個函數座位參數傳遞給另外一個函數,編寫一個 forEach 函數是至關明確了:工具
function forEach(callback, array) { for (var i = 0; i < array.length; i = i + 1) { callback(array[i], i); } }
這個函數接受執行 callback 函數,而且把數組的每一項做爲參數調用 callback 函數。
如今,在咱們的例子中,咱們想要對數組的每個元素去運行 addColour 方法,使用咱們的 forEach 咱們僅需一行就能完美執行~post
forEach(addColour, colours);
對數組中的每個元素調用一個方法是很是有用的一個工具,JavaScript 也實現了這麼一個特性組成,做爲數組對象的一個方法。所以咱們也可使用它來替代咱們的 forEach 方法,以下:
var colours = [ 'red', 'orange', 'yellow', 'green', 'blue', 'purple' ]; colours.forEach(addColour);
咱們能夠查找更多的方法在 MDN 的 JavaScript 參考文檔裏。
咱們的 forEach 已經很是好用了,可是扔有一些侷限性。若是回調函數返回一個值,那 forEach 只會忽略掉這個返回值。咱們能夠適當改造一下 forEach 函數,不管他返回什麼類型的值咱們均可以取得。咱們能夠獲得一個數組,它包含了咱們原始數字相對應的一些值。
讓咱們看看下面的例子,咱們有一個 ID 的數組,而後咱們想獲得他們每一個相對應的 DOM 元素集合。在程序上找到解決的方法,咱們可使用循環:
var ids = ['unicorn', 'fairy', 'kitten']; var elements = []; for (var i = 0; i < ids.length; i = i + 1) { elements[i] = document.getElementById(ids[i]); } // elements now contains the elements we are after
咱們想要闡明計算機是如何建立一個索引變量而且增長它--細節咱們不需多考慮。讓咱們使用循環就像 forEach 裏面同樣,而且把它複製給一個叫作 map 的變量。
var map = function(callback, array) { var newArray = []; for (var i = 0; i < array.length; i = i + 1) { newArray[i] = callback(array[i], i); } return newArray; }
如今咱們有了一個 map 函數,能夠這麼實用它:
var getElement = function(id) { return document.getElementById(id); }; var elements = map(getElement, ids);
這個 map 函數小而簡單,可是它裏面執行了另外一個咱們的超級函數,只需在數組的一個入口調用它一次就能夠了,成倍的增加了函數的效率。
像 forEach 函數同樣,JavaScript 自己也實現了 map 函數,做爲數組對象的一個方法。咱們能夠調用這部分方法以下:
var ids = ['unicorn', 'fairy', 'kitten']; var getElement = function(id) { return document.getElementById(id); }; var elements = ids.map(getElement, ids);
嗯,map 函數事頗有用的。可是咱們想實現一個更有用的函數,若是咱們對全部的數組元素執行函數可是返回僅僅一個值。這聽起來可能有一點反常,函數返回一個值爲何會比返回多個值更有用?爲了尋找答案,咱們能夠先看看下面這個函數是如何工做的。
爲了解釋,咱們考慮兩個類似的問題:
給一個數組一些數值元素,而且計算他們的和;
給一個數組一些字符串元素,而且用空格符在他們直接鏈接起來。
如今咱們來看看一個愚蠢、微不足道的例子--事實是他們也確實如此。這對於我來講至關難忍受,可是若是咱們一旦學習到 reduce 函數是如何工做的,咱們就能夠把它應用在不少有趣的狀況下。
咱們再來看看程序上時如可解決這個問題的,仍是循環:
// Given an array of numbers, calculate the sum var numbers = [1, 3, 5, 7, 9]; var total = 0; for (i = 0; i < numbers.length; i = i + 1) { total = total + numbers[i]; } // total is 25 // Given an array of words, join them together with a space between each word. var words = ['sparkle', 'fairies', 'are', 'amazing']; var sentence = ''; for (i = 0; i < words.length; i++) { sentence = sentence + ' ' + words[i]; } // ' sparkle fairies are amazing'
兩個方案都是同樣的其實,他們都使用for循環去迭代,他們都有個執行變量(total 和 sentence),他們都對執行變量賦值一個初始值。
讓咱們把他們循環中的操做抽出來寫一個函數:
var add = function(a, b) { return a + b; } // Given an array of numbers, calculate the sum var numbers = [1, 3, 5, 7, 9]; var total = 0; for (i = 0; i < numbers.length; i = i + 1) { total = add(total, numbers[i]); } // total is 25 function joinWord(sentence, word) { return sentence + ' ' + word; } // Given an array of words, join them together with a space between each word. var words = ['sparkle', 'fairies', 'are', 'amazing']; var sentence = ''; for (i = 0; i < words.length; i++) { sentence = joinWord(sentence, words[i]); } // 'sparkle fairies are amazing'
如今,它變得簡潔有用了,內部函數把執行變量做爲他的第一個參數,而後當前遍歷到的數組元素值做爲第二個參數。如今咱們讓它更加簡潔,咱們把凌亂的 for 循環放到函數裏面。
var reduce = function(callback, initialValue, array) { var working = initialValue; for (var i = 0; i < array.length; i = i + 1) { working = callback(working, array[i]); } return working; };
如今咱們又個閃閃發亮的 reduce 新函數了,讓咱們來使用使用它:
var total = reduce(add, 0, numbers); var sentence = reduce(joinWord, '', words);
就像 forEach 和 map 函數,reduce 函數也是 JavaScript 數組對象的標準方法之一。
咱們能夠這麼使用它:
var total = numbers.reduce(add, 0); var sentence = words.reduce(joinWord, '');
咱們能夠在 MDN 上查閱更多關於 reduce 函數的組成。
正如咱們前面所說起,他們都是很簡單的例子--add 和 joinWord 函數都很是地簡單。更小,更簡單的函數易於思考和測試。當咱們把兩個小而簡單的函數結合起來的時候(就像 add 和 reduce),他的結果比起編寫一個大而複雜的函數來講仍然很好解釋。
可是,正如我前面所說,咱們能夠把不少方法結合起來作一些更有意思的事情。
讓咱們嘗試更復雜一些,咱們把一個數據對象用 map 和 reduce 函數轉化成一個 HTML 列表,數據以下:
var ponies = [ [ ['name', 'Fluttershy'], ['image', 'http://tinyurl.com/gpbnlf6'], ['description', 'Fluttershy is a female Pegasus pony and one of the main characters of My Little Pony Friendship is Magic.'] ], [ ['name', 'Applejack'], ['image', 'http://tinyurl.com/gkur8a6'], ['description', 'Applejack is a female Earth pony and one of the main characters of My Little Pony Friendship is Magic.'] ], [ ['name', 'Twilight Sparkle'], ['image', 'http://tinyurl.com/hj877vs'], ['description', 'Twilight Sparkle is the primary main character of My Little Pony Friendship is Magic.'] ] ];
這些數據不是十分整潔,若是把裏面的數組變成對象會更清晰。不過,在以前咱們能夠 reduce 函數去估算一些簡單的值就像數字或者字符串,可是沒人敢說 reduce 返回的值必定簡單。咱們能夠把它使用在 對象,數組 或者甚至是 DOM 元素 上面。讓咱們創造一個函數把內部的數組(像 ['name', 'Fluttershy'])以鍵值對的形式添加到一個對象之中。
var addToObject = function(obj, arr) { obj[arr[0]] = arr[1]; return obj; };
使用 addToObject 函數,咱們能夠把每一個數組轉化成對象:
var ponyArrayToObject = function(ponyArray) { return reduce(addToObject, {}, ponyArray); };
而後使用 map 函數把整個數組轉化的更簡潔清晰:
var tidyPonies = map(ponyArrayToObject, ponies);
如今咱們有了一個對象組成的數組,能夠從Thomas Fuchs’ tweet-sized templating engine得到一些幫助,咱們能夠再使用 reduce 函數把它轉換成 HTML 片斷。模版函數接受一個模版字符串和一個對象,他會把字符串當中 mustache-wrapped 格式的部分(像, {name} 或者 {image}))用對象中包含的屬性值類替換。好比:
var data = { name: "Fluttershy" }; t("Hello {name}!", data); // "Hello Fluttershy!" data = { who: "Fluttershy", time: Date.now() }; t("Hello {name}! It's {time} ms since epoch.", data); // "Hello Fluttershy! It's 1454135887369 ms since epoch."
因此,若是咱們想要吧對象轉換成列表項,咱們能夠這麼作:
var ponyToListItem = function(pony) { var template = '<li><img src="{image}" alt="{name}"/>' + '<div><h3>{name}</h3><p>{description}</p>' + '</div></li>'; return t(template, pony); };
在上面咱們把對象轉換成了 html 片斷,可是若是想轉換整個數組,咱們就須要 reduce 和 joinWord 方法:
var ponyList = map(ponyToListItem, tidyPonies); var html = '<ul>' + reduce(joinWord, '', ponyList) + '</ul>';
咱們能夠在http://jsbin.com/wuzini/edit?html,js,output看到整個運行結果。
當你理解 reduce 和 map 方法以後,你就再也不須要老舊的 for 循環了。事實上,若是你決定在你的下一個項目上徹底不使用 for 循環將會是一個頗有意思的挑戰。當你使用 reduce 和 map 愈來愈多的時候,你就會注意還有沒有更多的部分能夠被抽象出來。一些常見的包括過濾filtering,或者plucking。這些部分被使用的愈來愈頻繁,人們把他們放到一個函數式編程的庫裏面,有一些流行的庫包括:
Lodash, and
-----------------
<br/>
未亡待續...
[閱讀下一節~]
原文地址歡迎關注blog~