1.非 FP(函数编程) 集合处理方法
forEach()
, some()
, every()
处理集合的方法,看起来像函数编程中的方法,实际上却不是的;
forEach()
迭代调用回调函数会产生副作用。some()
,every()
不可撤销的将集合变为一个 true/false 结果,所以下面将跳过这3个方法。
2.Map
手动实现map()
function map(arr, mapperFn) {
var newList = [];
for (let idx = 0; idx < arr.length; idx++) {
newList.push(
// 为了和内置的map()函数签名一致,传入3个参数
mapperFn(arr[idx], idx, arr)
);
}
return newList;
}
// 例子
map(["1", "2", "3"], unary(parseInt)); // [1,2,3]
// unary()是前面章节中的一个函数
var unary =
fn =>
arg =>
fn(arg);
原生map(),我们可以先将functions集合和另一个函数组合,然后再执行:
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这个单词在JS中有2层意思:
- 过滤不想要的,留下想要的(filtering out)
- 过滤想要的,留下不想要的(filtering in)
filter()内的回调函数返回布尔值,我们把这样函数称之为 predicate functions
.
手动实现:
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是函数编程中最重要的工具,像多合一瑞士军刀一样。
它有2种形式,一种提供initialValue
,一种不提供。
1.单独实现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个参数
acc = reducerFn(acc, arr[idx], idx, arr);
}
return acc;
}
2.使用 reduce 模拟 map 的功能
这里面的技巧是,reduce的初始值可以为一个空的数组[].
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]
上面标记注意的地方,其写法为:
(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.高级集合操作
下面介绍许多库中常用的一些函数, unique()
, flatten()
, zip()
, merge()
...
1.去重 unique()
去除数组中重复的items有几种实现方法。
1.使用 filter + indexOf
var unique =
arr =>
arr.filter(
(v, idx) =>
arr.indexOf(v) === idx
// 利用值的位置和索引位置对比
);
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.将嵌套的数组打平 Flatten
比如将:
[[1, 2, 3], 4, [5, [6, 7]]]
// 转换为
[1, 2, 3, 4, 5, 6, 7]
// 或者
[1, 2, 3, 4, 5, [6, 7]]
1.使用 reduce + 递归 直接全部打平
var flatten =
arr =>
arr.reduce(
(list, v) => (
// 判断内部item是否为数组
// 如果是则递归调用flatten
// 不是则直接添加到list数组中
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.对打平的层次添加一个 depth
// 默认打开层次为无限, 即直接全部打开
var flatten =
(arr, depth = Infinity) =>
arr.reduce(
(list, v) =>
list.concat(
depth > 0 ?
(depth > 1 && Array.isArray(v) ?
flatten(v, depth - 1) :
v
) :
[v]
)
, []);
实例:
// 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, 默认为都打开
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结合map使用对实际数据进行操作:
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()实现:
var flatMap =
(arr, mapperFn) =>
arr.reduce(
(list, v) =>
list.concat(mapperFn(v))
, []);
3.交替取出2个数组中的元素 zip
有时候我们需要交替取出2个数组中的元素组成一个新的item, 然后返回一个新的数组。
比如:
zip([1, 2, 3, 4], [5, 6, 7, 8, 9]);
// [ [1,5], [2,6], [3,7], [4,8] ]
上面实例可以看出,以长度小的那个数组长度作为返回数组长度
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个数组merge
交替添加到一个新的数组中
mergeLists( [1,3,5,7,9], [2,4,6,8,10] );
// [1,2,3,4,5,6,7,8,9,10]
可以看出上面的实例可以理解为 zip() -> flatten()
, 但是这有个缺点就是必须以长度短的为基准,下面实现:先交替添加,剩下的直接添加到数组里面。
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(..);
这种声明式的好处就是思路十分清晰,确定就是每次操作,都要对数组遍历一遍,这要势必会影响性能。
融合可以处理相连的操作符,减少遍历的次数,下面对相连的map()进行融合。
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都是一元的,可以想到使用前面的学过的 compose 或者 pipe
words.map(
compose(elide, upper, removeInvalidChars)
);
// 或者
words.map(
pipe(removeInvaildChars, upper, elide)
);
总结
通过本章学习了:
- map, filter, reduce的手工实现
- unique() 函数
- flatten() 函数
- zip()
- merge()
- 以及融合对相连的map进行合并