在全部後 ES6 時代的數組方法中,我以爲最難理解的就是Array.reduce()
。html
從表面上看,它彷佛是一個簡單無趣的方法,並無太大做用。 可是在不起眼的外表之下,Array.reduce()
其實是對開發人員工具包的強大而靈活的補充。前端
今天,咱們就來研究一下經過Array.reduce()
能夠完成的一些有意思的事情。數組
大部分現代的數組方法都返回一個新的數組,而 Array.reduce()
更加靈活。它能夠返回任意值,它的功能就是將一個數組的內容聚合成單個值。瀏覽器
這個值能夠是數字、字符串,甚至能夠是對象或新數組。這就是一直難住個人部分,我沒想到它這麼靈活!bash
Array.reduce()
接受兩個參數:一個是對數組每一個元素執行的回調方法,一個是初始值。微信
這個回調接收4個參數,前兩個參數是:accumulator
是當前聚合值,current
是數組循環時的當前元素。不管你返回什麼值,都將做爲累加器提供給循環中的下一個元素。初始值將做爲第一次循環的累加器。函數
var myNewArray = [].reduce(function (accumulator, current) {
return accumulator;
}, starting);
複製代碼
讓咱們來看幾個實際例子。工具
假設你想把一組數字加在一塊兒。使用Array.forEach()
大概能夠這麼作:性能
var total = 0;
[1, 2, 3].forEach(function (num) {
total += num;
});
複製代碼
這是Array.reduce()
用得最多的例子了。我發現* accumulator *這個單詞讓人困惑,因此在示例中我改成sum
,由於這裏就是求和的意思。ui
var total = [1, 2, 3].reduce(function (sum, current) {
return sum + current;
}, 0);
複製代碼
這裏傳入0
做爲初始值。
在回調裏,將當前值加入到 sum
,第一輪循環時它的值是初始值0
,而後變成1
(初始值0
加上當前元素值1
),而後變成3
(累加值 1
加上當前元素值 2
),以此類推
假設有一個wizards
數組:
var wizards = [
{
name: 'Harry Potter',
house: 'Gryfindor'
},
{
name: 'Cedric Diggory',
house: 'Hufflepuff'
},
{
name: 'Tonks',
house: 'Hufflepuff'
},
{
name: 'Ronald Weasley',
house: 'Gryfindor'
},
{
name: 'Hermione Granger',
house: 'Gryfindor'
}
];
複製代碼
你想建立一個僅包含住在 Hufflepuff 的巫師名字的新數組。一個可行的方法是使用Array.filter()
方法獲取 house
屬性爲Hufflepuff
的 wizards
。而後用Array.map()
方法建立一個只包含過濾後對象的name
屬性的新數組。
var hufflepuff = wizards.filter(function (wizard) {
return wizard.house === 'Hufflepuff';
}).map(function (wizard) {
return wizard.name;
});
複製代碼
使用Array.reduce()
方法,咱們能夠用一步獲得一樣的結果,提升了性能。傳遞一個空數組[]
做爲初始值。每次循環時判斷wizard.house
是否爲Hufflepuff
。若是是,就加入到newArr
中(即accumulator
),不然啥也不作。
不管判斷條件是否成立,最後都返回 newArr
做爲下一次循環的accumulator
。
var hufflepuff = wizards.reduce(function (newArr, wizard) {
if (wizard.house === 'Hufflepuff') {
newArr.push(wizard.name);
}
return newArr;
}, []);
複製代碼
那麼,若是想建立一個由住在 Hufflepuff 的巫師組成的無序列表要怎麼作呢?此次不是給Array.reduce()
傳一個空數組做爲初始值了,而是一個名爲 html
的空字符串''
。
若是wizard.house
等於 Hufflepuff
,咱們就將wizard.name
用列表項li
包裹起來,再拼接到html
字符串裏。而後返回html
做爲下一次循環的accumulator
。
var hufflepuffList = wizards.reduce(function (html, wizard) {
if (wizard.house === 'Hufflepuff') {
html += '<li>' + wizard.name + '</li>';
}
return html;
}, '');
複製代碼
在Array.reduce()
先後添加無序列表的開始和結束標記,就能夠把它插入到 DOM 中了。
var hufflepuffList = '<ul>' + wizards.reduce(function (html, wizard) {
if (wizard.house === 'Hufflepuff') {
html += '<li>' + wizard.name + '</li>';
}
return html;
}, '') + '</ul>';
複製代碼
lodash 有個 groupBy()
方法,能夠將數組元素按照某個標準分組。
假設你有一個數字數組。
若是你想把numbers
數組中的元素按照整數部分的值分組,用 lodash 能夠這樣作:
var numbers = [6.1, 4.2, 6.3];
// 返回 {'4': [4.2], '6': [6.1, 6.3]}
_.groupBy(numbers, Math.floor);
複製代碼
若是你有一個單詞數組,你想根據 words
中的單詞長度分組,你能夠這樣作:
var words = ['one', 'two', 'three'];
// 返回 {'3': ['one', 'two'], '5': ['three']}
_.groupBy(words, 'length');
複製代碼
Array.reduce()
實現 groupBy()
函數你能夠用Array.reduce()
方法實現一樣的功能。
咱們來建立一個工具函數groupBy()
,接受數組和分組條件做爲參數。在groupBy()
內部,在數組上執行Array.reduce()
,傳一個空對象{}
做爲初始值,而後返回結果。
var groupBy = function (arr, criteria) {
return arr.reduce(function (obj, item) {
// 省略代碼
}, {});
};
複製代碼
在 Array.reduce()
回調函數內部,咱們會判斷criteria
是函數仍是 item
的屬性。而後獲取當前item
的值。
若是obj
中還不存在這個屬性,則建立它,並將一個空數組賦值給它。最後,將item
添加到 key
的數組中,再返回該對象做爲下一次循環的accumulator
。
var groupBy = function (arr, criteria) {
return arr.reduce(function (obj, item) {
// 判斷criteria是函數仍是屬性名
var key = typeof criteria === 'function' ? criteria(item) : item[criteria];
// 若是屬性不存在,則建立一個
if (!obj.hasOwnProperty(key)) {
obj[key] = [];
}
// 將元素加入數組
obj[key].push(item);
// 返回這個對象
return obj;
}, {});
};
複製代碼
還記得前面的wizards
數組嗎?
var wizards = [
{
name: 'Harry Potter',
house: 'Gryfindor'
},
{
name: 'Cedric Diggory',
house: 'Hufflepuff'
},
{
name: 'Tonks',
house: 'Hufflepuff'
},
{
name: 'Ronald Weasley',
house: 'Gryfindor'
},
{
name: 'Hermione Granger',
house: 'Gryfindor'
}
];
複製代碼
若是還有另外一份數據,每一個巫師得到的的積分對象:
var points = {
HarryPotter: 500,
CedricDiggory: 750,
RonaldWeasley: 100,
HermioneGranger: 1270
};
複製代碼
假設你想把兩份數據合併到一個數組,也就是把 points
數值添加到每一個巫師對象上。你會怎麼作?
Array.reduce()
方法特別適合!
var wizardsWithPoints = wizards.reduce(function (arr, wizard) {
// 移除巫師名字中的空格,用來獲取對應的 points
var key = wizard.name.replace(' ', '');
// 若是wizard有points,則加上它,不然設置爲0
if (points[key]) {
wizard.points = points[key];
} else {
wizard.points = 0;
}
// 把wizard對象加入到新數組裏
arr.push(wizard);
// 返回這個數組
return arr;
}, []);
複製代碼
其實這裏用Array.map
也很方便實現。
若是你想合併兩個來源的數據到一個對象中,也就是巫師的名字做爲屬性名,house 和 points 做爲屬性值,要怎麼作呢?一樣, Array.reduce()
很合適。
var wizardsAsAnObject = wizards.reduce(function (obj, wizard) {
// 移除巫師名字中的空格,用來獲取對應的 points
var key = wizard.name.replace(' ', '');
// 若是wizard有points,則加上它,不然設置爲0
if (points[key]) {
wizard.points = points[key];
} else {
wizard.points = 0;
}
// 刪除 name 屬性
delete wizard.name;
// 把 wizard 數據添加到新對象中
obj[key] = wizard;
// 返回該對象
return obj;
}, {});
複製代碼
Array.reduce()
真香Array.reduce()
方法從我曾經認爲不堪大用的東西,變成我最喜歡的 JavaScript 方法。那麼,你應該使用它嗎?何時能夠用?
Array.reduce()
方法有着良好的瀏覽器支持。全部的現代瀏覽器都支持,包括 IE9 及以上。移動端瀏覽器也在很早以前就支持了。若是你還須要支持更老的瀏覽器,你能夠添加一個 polyfill 來支持到 IE6。
Array.reduce()
最大的槽點可能就是對於歷來沒接觸過的人來講有點費解。組合使用Array.filter()
和Array.map()
執行起來更慢,而且包含多餘的步驟,可是更容易閱讀,從方法名能夠明顯看出它要作的事情。
儘管如此,有時候Array.reduce()
也可讓複雜的事情看起來更簡單。 groupBy()
工具函數就是個很好的例子。
最後,它應該成爲你的工具箱裏的另外一個工具,一個使用得當就威力無窮的工具。