33-js-concepts之9. 高阶函数
1.概念
一个函数接受另一个函数作为参数,这就是高阶函数。
e.g.
function add(x, y, f) {
return f(x) + f(y);
}
add(-5, 6, Math.abs); // 11
2. ES5中新增的Array方法
1) forEach
[1, 2, 3, 4].forEach(console.log);
// 1 0 [1, 2, 3, 4]
// 2 1 [1, 2, 3, 4]
// 3 2 [1, 2, 3, 4]
// 4 3 [1, 2, 3, 4]
由此可见,forEach参数中的回调函数接收三个参数,分别是value, index, array,即元素值、元素索引、数组本身。对此,我们有:
( 注意index在第二个参数位置)
var sum = 0;
[1, 2, 3, 4].forEach(function (item, index, array) {
console.log(array[index] == item); // true
sum += item;
});
alert(sum); // 10
forEach除了接收一个回调函数,还可以接收一个上下文参数(可选,默认是全局对象window),即:
array.forEach(callback,[ thisObject])
var database = {
users: ["张含韵", "江一燕", "李小璐"],
sendEmail: function (user) {
if (this.isValidUser(user)) {
console.log("你好," + user);
} else {
console.log("抱歉,"+ user +",你不是本家人");
}
},
isValidUser: function (user) {
return /^张/.test(user);
}
};
// 给每个人发邮件
database.users.forEach( // database.users中人遍历
database.sendEmail, // 发送邮件
database // 使用database代替上面标红的this
);
// 结果:
// 你好,张含韵
// 抱歉,江一燕,你不是本家人
// 抱歉,李小璐,你不是本家人
2) map
即“映射”,callback要有返回值,否则会返回一个全是undefined的数组。
array.map(callback,[ thisObject]);
可以利用map获得对象数组中的特定属性值:
var users = [
{name: "张含韵", "email": "zhang@email.com"},
{name: "江一燕", "email": "jiang@email.com"},
{name: "李小璐", "email": "li@email.com"}
];
var emails = users.map(function (user) { return user.email; });
console.log(emails.join(", ")); // zhang@email.com, jiang@email.com, li@email.com
3) filter
返回过滤后的新数组。callback函数需要返回值,弱等于Boolean值。
var data = [0, 1, 2, 3];
var arrayFilter = data.filter(function(item) {
return item;
});
console.log(arrayFilter); // [1, 2, 3]
var emailsZhang = users
// 获得邮件
.map(function (user) { return user.email; })
// 筛选出zhang开头的邮件
.filter(function(email) { return /^zhang/.test(email); });
console.log(emailsZhang.join(", ")); // zhang@email.com
4) some
数组中的某些项是否符合条件(至少1个),只要发现一个符合立刻返回true。返回值为true或false。
var scores = [5, 8, 3, 10];
var current = 7;
function higherThanCurrent(score) {
return score > current;
}
if (scores.some(higherThanCurrent)) {
alert("朕准了!");
}
// 朕准了!
5) every
和some相反,要全部符合条件才返回true,否则返回false。
if (scores.every(higherThanCurrent)) {
console.log("朕准了!");
} else {
console.log("来人,拖出去斩了!");
}
// 来人,拖出去斩了!
6) indexOf
略
7) lastIndexOf
略
8) reduce
callback函数接受4个参数:之前值、当前值、索引值以及数组本身。initialValue参数可选,表示初始值。若指定,则当作最初使用的previous值;如果缺省,则使用数组的第一个元素作为previous初始值,同时current往后排一位,相比有initialValue值少一次迭代。
array.reduce(callback[, initialValue])
var sum = [1, 2, 3, 4].reduce(function (previous, current, index, array) {
return previous + current;
});
console.log(sum); // 10
说明:
因为initialValue不存在,因此一开始的previous值等于数组的第一个元素。
从而current值在第一次调用的时候就是2.
最后两个参数为索引值index以及数组本身array。
// 初始设置
previous = initialValue = 1, current = 2
// 第一次迭代
previous = (1 + 2) = 3, current = 3
// 第二次迭代
previous = (3 + 3) = 6, current = 4
// 第三次迭代
previous = (6 + 4) = 10, current = undefined (退出)
二维数组扁平化:
var matrix = [
[1, 2],
[3, 4],
[5, 6]
];
// 二维数组扁平化
var flatten = matrix.reduce(function (previous, current) {
return previous.concat(current);
});
console.log(flatten); // [1, 2, 3, 4, 5, 6]
类似的reduceRight,从数组末尾开始:
var data = [1, 2, 3, 4];
var specialDiff = data.reduceRight(function (previous, current, index) {
if (index == 0) {
return previous + current;
}
return previous - current;
});
console.log(specialDiff); // 0
// 初始设置
index = 3, previous = initialValue = 4, current = 3
// 第一次迭代
index = 2, previous = (4- 3) = 1, current = 2
// 第二次迭代
index = 1, previous = (1 - 2) = -1, current = 1
// 第三次迭代
index = 0, previous = (-1 + 1) = 0, current = undefined (退出)
3.Reducer深入
首先是基本函数:
var words = [ "You", "have", "written", "something", "very", "interesting" ];
function strUppercase(str) { return str.toUpperCase(); }
function isLongEnough(str) {
return str.length >= 5;
}
function isShortEnough(str) {
return str.length <= 10;
}
要对数组过滤,可以直接map、连续filter,但显得过于重复,且每个 filter(..) 方法都会产生一个单独的 observable 值,数据量大时就会出现性能问题。所以可以使用reduce,initialValue设为空数组[]作为初始的list:
function strUppercaseReducer(list,str) {
return list.concat( [strUppercase( str )] );
}
function isLongEnoughReducer(list,str) {
if (isLongEnough( str )) return list.concat( [str] );
return list;
}
function isShortEnoughReducer(list,str) {
if (isShortEnough( str )) return list.concat( [str] );
return list;
}
除了判断函数不同其他基本相同,所以可以利用闭包把判断函数作为参数,建立一个filterReducer,同理还有mapReducer:
function filterReducer(predicateFn) {
return function reducer(list, val) {
if (predicateFn(val)) {
return list.concat( [val] );
}
return list;
}
}
var isLongEnoughReducer = filterReducer( isLongEnough );
var isShortEnoughReducer = filterReducer( isShortEnough );
function mapReducer(fn) {
return function reducer(list, val) {
return list.concat( [fn(val)] );
}
}
var strToUppercaseReducer = mapReducer( strUppercase );
调用链就可以写成这样:
words
.reduce( strUppercaseReducer, [] )
.reduce( isLongEnoughReducer, [] )
.reduce( isShortEnough, [] )
.reduce( strConcat, "" );
// "WRITTENSOMETHING"
提取共用组合逻辑
仔细观察上面的 mapReducer(..) 和 filterReducer(..) 函数。你发现共享功能了吗?
这部分:
return list.concat( .. );
// 或者
return list;
所以还可以再提取出一个listCombination函数出来:
function listCombination(list,val) { return list.concat( [ val ] ); }
所以最新的filterReducer和mapReducer如下:
function filterReducer(predicateFn) {
return function reducer(list, val) {
if (predicateFn(val)) {
return listCombination( list, val );
}
return list;
}
}
function mapReducer(fn) {
return function reducer(list, val) {
return listCombination( list, fn(val) );
}
}
进一步,可以使用不同的类似listCombination的工具函数,所以再对reducer进行改造如下:
function filterReducer(predicateFn,combinationFn) {
return function reducer(list,val){
if (predicateFn( val )) return combinationFn( list, val );
return list;
};
}
function mapReducer(mapperFn,combinationFn) {
return function reducer(list,val){
return combinationFn( list, mapperFn( val ) );
};
}
var strToUppercaseReducer = mapReducer( strUppercase, listCombination );
var isLongEnoughReducer = filterReducer( isLongEnough, listCombination );
var isShortEnoughReducer = filterReducer( isShortEnough, listCombination );
将这些实用函数定义为接收两个参数而不是一个参数不太方便组合,因此我们使用我们的 curry(..) (柯里化)方法:
var curriedMapReducer = curry( function mapReducer(mapperFn,combinationFn){
return function reducer(list,val){
return combinationFn( list, mapperFn( val ) );
};
} );
var curriedFilterReducer = curry( function filterReducer(predicateFn,combinationFn){
return function reducer(list,val){
if (predicateFn( val )) return combinationFn( list, val );
return list;
};
} );
var strToUppercaseReducer =
curriedMapReducer( strUppercase )( listCombination );
var isLongEnoughReducer =
curriedFilterReducer( isLongEnough )( listCombination );
var isShortEnoughReducer =
curriedFilterReducer( isShortEnough )( listCombination );
参考资料:
ES5中新增的Array方法详细说明
翻译连载 | 附录 A:Transducing(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇