1.非 FP(函數(shù)編程) 集合處理方法
forEach(), some(), every()處理集合的方法,看起來像函數(shù)編程中的方法,實(shí)際上卻不是的;
forEach() 迭代調(diào)用回調(diào)函數(shù)會(huì)產(chǎn)生副作用。some(),every()不可撤銷的將集合變?yōu)橐粋€(gè) true/false 結(jié)果,所以下面將跳過這3個(gè)方法。
2.Map
手動(dòng)實(shí)現(xiàn)map()
function map(arr, mapperFn) {
var newList = [];
for (let idx = 0; idx < arr.length; idx++) {
newList.push(
// 為了和內(nèi)置的map()函數(shù)簽名一致,傳入3個(gè)參數(shù)
mapperFn(arr[idx], idx, arr)
);
}
return newList;
}
// 例子
map(["1", "2", "3"], unary(parseInt)); // [1,2,3]
// unary()是前面章節(jié)中的一個(gè)函數(shù)
var unary =
fn =>
arg =>
fn(arg);
原生map(),我們可以先將functions集合和另一個(gè)函數(shù)組合,然后再執(zhí)行:
var increment = v => ++v;
var decrement = v => --v;
var square = v => v * v;
var double = v => v * 2;
[increment, decrement, square]
.map(fn => compose(fn, double))
.map(fn => fn(3)); // [7, 5, 36]
3.Filter
filter這個(gè)單詞在JS中有2層意思:
- 過濾不想要的,留下想要的(filtering out)
- 過濾想要的,留下不想要的(filtering in)
filter()內(nèi)的回調(diào)函數(shù)返回布爾值,我們把這樣函數(shù)稱之為 predicate functions.
手動(dòng)實(shí)現(xiàn):
function filter(arr, predicateFn) {
var newList = [];
for (let idx = 0; idx < arr.length; idx++) {
if (predicateFn(arr[idx], idx, arr)) {
newList.push(arr[idx]);
}
}
return newList;
}
4.Reduce
Reduce是函數(shù)編程中最重要的工具,像多合一瑞士軍刀一樣。
它有2種形式,一種提供initialValue,一種不提供。


1.單獨(dú)實(shí)現(xiàn)Reduce()
function reduce(arr, reducerFn, initialValue) {
var acc, startIdx;
// 提供initialValue的情況
if (arguments.length === 3) {
acc = initialValue;
startIdx = 0;
} else if (arr.length > 0) { // 不提供initialValue
arr = arr[0];
startIdx = 1;
} else {
throw new Error("Must provide at least one value");
}
for (let idx = startIdx; idx < arr.length; idx++) {
// 注意這里是4個(gè)參數(shù)
acc = reducerFn(acc, arr[idx], idx, arr);
}
return acc;
}
2.使用 reduce 模擬 map 的功能
這里面的技巧是,reduce的初始值可以為一個(gè)空的數(shù)組[].
var double = v => v * 2;
[1, 2, 3].map(double); // [2, 4, 6]
// list的初始值為[]
[1, 2, 3].reduce(
(list, v) => (
list.push(double(v)), // 注意
list
), []
);
// [2, 4, 6]
上面標(biāo)記注意的地方,其寫法為:
(list, v) => (
list.push(double(v));
return list;
)
3.使用 reduce 模擬 filter 的功能
其原理和模擬map類似
var isOdd = v => v % 2 === 1;
[1, 2, 3, 4, 5].filter(isOdd); // [1, 3, 5]
[1, 2, 3, 4, 5].reduce(
(list, v) => (
isOdd(v) ? list.push(v) : undefined,
list
), []
); // [1, 3, 5]
5.高級(jí)集合操作
下面介紹許多庫中常用的一些函數(shù), unique(), flatten(), zip(), merge()...
1.去重 unique()
去除數(shù)組中重復(fù)的items有幾種實(shí)現(xiàn)方法。
1.使用 filter + indexOf
var unique =
arr =>
arr.filter(
(v, idx) =>
arr.indexOf(v) === idx
// 利用值的位置和索引位置對(duì)比
);
2.瑞士軍刀 reduce + indexOf
var unique =
arr =>
arr.reduce(
(list, v) =>
list.indexOf(v) === -1 ?
(list.push(v), list) : list
, []);
3.ES6新集合類型 Set
var unique =
arr =>
[...new Set(arr)];
unique([1, 2, 3, 3, 4, 4, 1])
// [1, 2, 3, 4]
2.將嵌套的數(shù)組打平 Flatten
比如將:
[[1, 2, 3], 4, [5, [6, 7]]]
// 轉(zhuǎn)換為
[1, 2, 3, 4, 5, 6, 7]
// 或者
[1, 2, 3, 4, 5, [6, 7]]
1.使用 reduce + 遞歸 直接全部打平
var flatten =
arr =>
arr.reduce(
(list, v) => (
// 判斷內(nèi)部item是否為數(shù)組
// 如果是則遞歸調(diào)用flatten
// 不是則直接添加到list數(shù)組中
list.concat(Array.isArray(v) ? flatten(v) : v)
),
[]
);
flatten( [[0,1],2,3,[4,[5,6,7],[8,[9,[10,[11,12],13]]]]] );
// [0,1,2,3,4,5,6,7,8,9,10,11,12,13]
2.對(duì)打平的層次添加一個(gè) depth
// 默認(rèn)打開層次為無限, 即直接全部打開
var flatten =
(arr, depth = Infinity) =>
arr.reduce(
(list, v) =>
list.concat(
depth > 0 ?
(depth > 1 && Array.isArray(v) ?
flatten(v, depth - 1) :
v
) :
[v]
)
, []);
實(shí)例:
// 0 表示不打開
flatten( [[0,1],2,3,[4,[5,6,7],[8,[9,[10,[11,12],13]]]]], 0 );
// [[0,1],2,3,[4,[5,6,7],[8,[9,[10,[11,12],13]]]]]
flatten( [[0,1],2,3,[4,[5,6,7],[8,[9,[10,[11,12],13]]]]], 1 );
// [0,1,2,3,4,[5,6,7],[8,[9,[10,[11,12],13]]]]
flatten( [[0,1],2,3,[4,[5,6,7],[8,[9,[10,[11,12],13]]]]], 2 );
// [0,1,2,3,4,5,6,7,8,[9,[10,[11,12],13]]]
flatten( [[0,1],2,3,[4,[5,6,7],[8,[9,[10,[11,12],13]]]]], 3 );
// [0,1,2,3,4,5,6,7,8,9,[10,[11,12],13]]
flatten( [[0,1],2,3,[4,[5,6,7],[8,[9,[10,[11,12],13]]]]], 4 );
// [0,1,2,3,4,5,6,7,8,9,10,[11,12],13]
flatten( [[0,1],2,3,[4,[5,6,7],[8,[9,[10,[11,12],13]]]]], 5 );
// [0,1,2,3,4,5,6,7,8,9,10,11,12,13]
// 不添加depth, 默認(rèn)為都打開
flatten( [[0,1],2,3,[4,[5,6,7],[8,[9,[10,[11,12],13]]]]] );
// [0,1,2,3,4,5,6,7,8,9,10,11,12,13]
3.flatMap() 通常使用方法
通常flatten結(jié)合map使用對(duì)實(shí)際數(shù)據(jù)進(jìn)行操作:
var firstNames = [
{ name: "Jonathan", variations: [ "John", "Jon", "Jonny" ] },
{ name: "Stephanie", variations: [ "Steph", "Stephy" ] },
{ name: "Frederick", variations: [ "Fred", "Freddy" ] }
];
firstNames
.map(entry => [entry.name].concat(entry.variations))
// [ ["Jonathan","John","Jon","Jonny"], ["Stephanie","Steph","Stephy"],
// ["Frederick","Fred","Freddy"] ]
// 然后利用flatten得到所有的names
flatten(
firstNames
.map( entry => [entry.name].concat( entry.variations ) )
);
// ["Jonathan","John","Jon","Jonny","Stephanie","Steph","Stephy","Frederick",
// "Fred","Freddy"]
flatMap()實(shí)現(xiàn):
var flatMap =
(arr, mapperFn) =>
arr.reduce(
(list, v) =>
list.concat(mapperFn(v))
, []);
3.交替取出2個(gè)數(shù)組中的元素 zip
有時(shí)候我們需要交替取出2個(gè)數(shù)組中的元素組成一個(gè)新的item, 然后返回一個(gè)新的數(shù)組。
比如:
zip([1, 2, 3, 4], [5, 6, 7, 8, 9]);
// [ [1,5], [2,6], [3,7], [4,8] ]
上面實(shí)例可以看出,以長(zhǎng)度小的那個(gè)數(shù)組長(zhǎng)度作為返回?cái)?shù)組長(zhǎng)度
function zip(list1, list2) {
var zipped = [];
list1 = list1.slice();
list2 = list2.slice();
while(list1.length > 0 && list2.length > 0) {
zipped.push([list1.shift(), list2.shift()]);
}
return zipped;
}
4.交替合并2個(gè)數(shù)組merge
交替添加到一個(gè)新的數(shù)組中
mergeLists( [1,3,5,7,9], [2,4,6,8,10] );
// [1,2,3,4,5,6,7,8,9,10]
可以看出上面的實(shí)例可以理解為 zip() -> flatten(), 但是這有個(gè)缺點(diǎn)就是必須以長(zhǎng)度短的為基準(zhǔn),下面實(shí)現(xiàn):先交替添加,剩下的直接添加到數(shù)組里面。
function merge(list1, list2) {
var merged = [];
list1 = list1.slice();
list2 = list2.slice();
while (list1.length > 0 || list2.length > 0) {
if (list1.length > 0) {
merged.push( list1.shift() );
}
if (list2.length > 0) {
merged.push( list2.shift() );
}
}
return merged;
}
4.融合 Fusion
像這樣的操作
..
.filter(..)
.filter(..)
.map(..)
.map(..)
.map(..)
.reduce(..);
這種聲明式的好處就是思路十分清晰,確定就是每次操作,都要對(duì)數(shù)組遍歷一遍,這要?jiǎng)荼貢?huì)影響性能。
融合可以處理相連的操作符,減少遍歷的次數(shù),下面對(duì)相連的map()進(jìn)行融合。
var removeInvalidChars = str => str.replace(/[^\w]*/g, "");
var upper = text => text.toUpperCase();
var elide = str =>
str.length > 10?
str.slice(0, 7) + "..." :
str;
words;
// ["Mr.","Jones","isn't","responsible","for","this","disaster!"]
words
.map( removeInvalidChars )
.map( upper )
.map( elide );
// ["MR","JONES","ISNT","RESPONS...","FOR","THIS","DISASTER"]
由于上面的map都是一元的,可以想到使用前面的學(xué)過的 compose 或者 pipe
words.map(
compose(elide, upper, removeInvalidChars)
);
// 或者
words.map(
pipe(removeInvaildChars, upper, elide)
);
總結(jié)
通過本章學(xué)習(xí)了:
- map, filter, reduce的手工實(shí)現(xiàn)
- unique() 函數(shù)
- flatten() 函數(shù)
- zip()
- merge()
- 以及融合對(duì)相連的map進(jìn)行合并