前言
js中有很多高阶函数,例如 map, filter, every
等,还有 ES6
提供的find
等,熟练使用后能极大提高编写代码的效率。
「 那么什么样的函数是高阶函数 」
至少满足下列一个条件的函数:
- 接受一个或者多个函数作为参数
- 输出(返回)一个函数
JavaScript
中的高阶函数大多数都是接受一个函数作为参数,如下:
Array.prototype.func = function(callback(currentValue[, index[, array]]){
// ...
}[, thisArg])
callback
:对数组元素进行操作的回调函数currentValue
:正在处理的当前元素index
: 当前元素索引array
: 调用高阶函数的数组
thisArg
: 可选,执行callback
函数时绑定的this
forEach
用法
forEach
主要用于数组的简单遍历,基本使用如下:
arr = [1, 2, 3, 4, 5];
arr.forEach((item, index) => {
console.log(item, index);
})
// 相当与for循环
for(let i = 0; i < arr.length; i++) {
console.log(arr[i], i);
}
模拟实现
我们先考虑一下在 forEach
内部发生了什么,其实就类似 for
循环一样,运行了 arr.length
次回调函数,回调函数的参数时对应的元素的索引、元素值和数组本身。由于 forEach
还可以接收 thisArg
参数作为回调函数的上下文环境,因此我们可以使用 call/apply
来修改。
Array.prototype.myForEach = function(callbackFn) {
// 判断this合法性
if(this === null || this === undefined) {
throw new TypeError('cannot read property "myForEach" of null')
}
// 判断回调函数是否合法
if(Object.prototype.toString.call(callbackFn) !== "[object Function]") {
throw new TypeError(callbackFn + "is not a function")
}
var _arr = this, thisArg = arguments[1] || window;
for(var i = 0; i < _arr.length; i++) {
// 执行回调函数
callbackFn.call(thisArg, _arr[i], i, _arr);
}
}
map
用法
map
函数对数组的每个元素执行回调函数,并返回含有运算结果的新数组,基本使用如下:
const usersInfo = [
{name: 'jack', age: 24},
{name: 'lucy', age: 21},
{name: 'nikky', age: 30}
]
// 需求: 获取数组中所有人的姓名数组
// 1、使用for循环
var names = [];
for(var i = 0; i <usersInfo.length; i++) {
names.push(usersInfo[i].name);
}
// 使用map
names = usersInfo.map(user => user.name);
模拟实现
我们只需对上面的方法稍加修改,使其结果返回新的数组(此处省略掉异常判断);
Array.prototype.myMap = function(callback) {
// ...省略异常判断
var _arr = this, thisArg = arguments[1] || window, res = [];
for(var i = 0; i < _arr.length; i++) {
// 存储运算结果
res.push(callback.call(thisArg, _arr[i], i, _arr));
}
return res;
}
filter
用法
filter
就是过滤的意思,它对数组的每个元素执行回调函数,返回回到函数执行后的结果为 true
的元素。
arr = [1, 2, 3, 4, 5];
// 筛选能被2整除的元素
arr.filter(item => item % 2 === 0); // [2, 4]
js模拟实现
Array.prototype.myFilter = function(callback) {
var _arr = this, thisArg = arguments[1] || window, res = [];
for(var i = 0; i <_arr.length; i++) {
// 回调函数执行结果为true
if(callback.call(thisArg, _arr[i], i, _arr)) {
res.push(_arr[i]);
}
}
return res;
}
every
用法
every
并不是返回数组,而是返回布尔值,数组的每个元素执行回调函数,如果执行结果都是 true
,every
返回 true
,否则返回 false
。
arr = [1, 3, 4, 5, 7];
arr.every(item => item % 2 === 1); // false, 4是偶数
arr2 = [2, 4, 6, 8];
arr2.every(item => item % 2 === 0); // true
模拟实现
Array.prototype.myEvery = function(callback) {
var _arr = this, thisArg = arguments[1] || window;
// 初始标识为true,如果遇到一个回调返回false,直接返回false
var flag = true;
for(var i = 0; i < _arr.length; i++) {
// 回调函数返回值为false,中断循环
if(!callback.call(thisArg, _arr[i], _arr)) {
return false;
}
}
return flag;
}
some
some
并不是返回数组,而是返回布尔值,数组的每个元素执行回调函数,如果遇到执行结果是 true
,some
返回 true
,否则返回 false
。
模拟实现
Array.prototype.mySome = function(callback) {
var _arr = this, thisArg = arguments[1] || window;
// 初始标识为false,如果遇到一个回调返true,直接返回true
var flag = false;
for(var i = 0; i < _arr.length; i++) {
// 回调函数返回值为false,中断循环
if(callback.call(thisArg, _arr[i], _arr)) {
return true;
}
}
return flag;
}
find/findIndex
用法
find
和 findIndex
是 ES6
新添加的数组方法,返回满足回调函数的第一个数组元素/数组元素索引。当数组中没有满足回调函数的元素时,分别返回 undefined
和 -1
。
const usersInfo = [
{name: 'jack', age: 24},
{name: 'lucy', age: 21},
{name: 'nikky', age: 30}
]
- 返回name为lucy的年龄
在没有find方法的时候,实现类似的效果需要遍历数组,查到name=jacy后再找到年龄,使用find就方便很多:
const jacyAge = usersInfo.find(item => item.name === 'jacy').age;
- 返回name为nikky的索引
ES6以前查找数组元素的方法:indexOf, lastIndexOf, 但是这两个方法在查找对象的时候就无能为力
// 返回值-1,说明查不到
usersInfo.indexOf({name: 'jack', age: 24});
// 返回为1,成功查到lucy
usersInfo.findIndex(user => user.name === 'lucy');
模拟实现
find/findIndex
都是寻找到第一个满足回调函数的元素返回.
Array.prototype.myFind = function(callbackFn) {
var _arr = this, thisArg = arguments[1] || window;
// 遇到回调返回true,直接返回该数组元素
// 如果循环执行完毕,意味着所有回调返回值为false,最终结果为undefined
for (var i = 0; i<_arr.length; i++) {
// 回调函数执行为false,函数中断
if (callbackFn.call(thisArg, _arr[i], i, _arr)) {
return _arr[i];
}
}
return undefined;
}
reduce
用法
arr.reduce(callback(accumulator, currentValue[, index[, array]]){
}[, initialValue])
callback
:对数组元素进行操作的回调函数accumulator
:累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue
currentValue
:正在处理的当前元素- 当前元素的索引
- 调用高阶函数的数组
- initialValue:作为第一次调用函数的初始值。如果没有提供初始值,则使用数组中的第一个元素
没有初始值的空数组调用 reduce 会报错。
- 数组累加和
arr = [0, 1, 2, 3, 4]
arr.reduce((prev, current) => prev + current, 0)
- 累加对象数组和
objArr = [{x: 1}, {x:2}, {x:3}]
objArr.reduce((prev, current) => prev.x + current.x, 0)
上述代码返回的结果为NaN
,为什么?
上文提过 accumulator
它的值为上一次调用之后的累计值或初始值,因此第一次调用过后将 3 赋值给 accumulator
,不再具有 x
属性,因此最终返回 NaN
// 法一:先借助map将数值提取出来
objArr.map(obj => obj.x).((accu, current) => accu + current, 0)
// 法二:赋予初值,每次运行accu + current.x
objArr.reduce((accu, current) => accu + current.x, 0)
- 计算数组中每个元素出现的次数
var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
var countedNames = names.reduce(function (allNames, name) {
if (name in allNames) {
allNames[name]++;
} else {
allNames[name] = 1;
}
console.log(allNames, name)
return allNames;
}, {});
模拟实现
Array.prototype.myReduce = function(callbackFn) {
var _arr = this, accumulator = arguments[1];
var i = 0;
// 判断是否传入初始值
if (accumulator === undefined) {
// 没有初始值的空数组调用reduce会报错
if (_arr.length === 0) {
throw new Error('initVal and Array.length>0 need one')
}
// 初始值赋值为数组第一个元素
accumulator = _arr[i];
i++;
}
for (; i<_arr.length; i++) {
// 计算结果赋值给初始值
accumulator = callbackFn(accumulator, _arr[i], i, _arr)
}
return accumulator;
}