前言
JavaScript
中有很多高阶函数,例如 map、filter、every
等,还有 ES6
新提供的 find
等,熟练使用后能极大提高编写代码的效率。下面就一起来学习一下这些高阶函数,并使用原生 JS
模拟实现。
那么什么样的函数是高阶函数那?
至少满足下列一个条件的函数:
- 接受一个函数或者多个函数作为参数
- 输出一个函数
JavaScript
中的高阶函数大多都是接受一个函数作为参数,具体传参如下:
Array.prototype.func = function(callback(currentValue[, index[, array]]){ }[, thisArg]) 复制代码
callback
:对数组元素进行操作的回调函数
currentValue
:正在处理的当前元素- 当前元素的索引
- 调用高阶函数的数组
thisArg
:可选,执 行callback
函数时绑定的this
forEach
用法
forEach
主要用于数组的简单遍历,基本使用如下
arr = [1, 2, 3, 4] arr.forEach((val, index) => { console.log(val, index) }) // 相当于原来的for循环 for (var i = 0; i<arr.length; i++) { console.log(arr[i], i) } 复制代码
模拟实现
我们先来回想一下上面案例中,forEach
内部发生了什么,很简单,就是类似 for
循环一样,运行 arr.length
次回调函数,回调函数的参数是对应的元素索引、元素值和数组本身。那我们就可以模拟出大概的运行流程。
for (var i = 0; i<arr.length; i++) { callback(arr[i], i, arr) } 复制代码
由于 forEach
还可以接受 thisArg
参数作为回调函数的上下文环境,因此使用 call/apply
对上面代码略作修改。
callback.call(thisArg, arr[i], i, arr) 复制代码
通过上面分析,我们就可写出完整的模拟代码:
Array.prototype.myForEach = function (callbackFn) { // 判断this是否合法 if (this === null || this === undefined) { throw new TypeError("Cannot read property 'myForEach' of null"); } // 判断callbackFn是否合法 if (Object.prototype.toString.call(callbackFn) !== "[object Function]") { throw new TypeError(callbackFn + ' is not a function') } // 取到执行方法的数组对象和传入的this对象 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 users = [ { name: 'John', age: 34 }, { name: 'Amy', age: 20 }, { name: 'camperCat', age: 10 } ]; // 需求:取出users所有人的name,并存放在新数组中 // 不使用map names = [] for (var i = 0; i<users.length; i++){ names.push(users[i].name) } // map是对数组的每一个元素进行操作,因此上述代码可以使用map来进行简化 names = users.map(function (user) { return user.name }) // 如果学过箭头函数,还可以进一步简化 names = user.map(user => user.name) 复制代码
模拟实现
有了上面 forEach
的编写经验,map
只需要稍作修改,使其结果返回新的数组(这里省略掉异常判断)。
Array.prototype.myMap = function(callbackFn) { var _arr = this, thisArg = arguments[1] || window, res = []; for (var i = 0; i<_arr.length; i++) { // 存储运算结果 res.push(callbackFn.call(thisArg, _arr[i], i, _arr)); } return res; } 复制代码
filter
用法
filter
是过滤的意思,它对数组每个元素执行回调函数,返回回调函数执行结果为true
的元素。
// 返回偶数 arr = [1, 2, 3, 4, 5]; arr.filter(val => val % 2 == 0) 复制代码
模拟实现
与 map
的实现大同小异,map
返回执行回调后所有的元素,而 filter
只返回回调函数执行结果为 true
的元素。
Array.prototype.myFilter = function(callbackFn) { var _arr = this, thisArg = arguments[1] || window, res = []; for (var i = 0; i<_arr.length; i++) { // 回调函数执行为true if (callbackFn.call(thisArg, _arr[i], i, _arr)) { res.push(_arr[i]); } } return res; } 复制代码
every
用法
every
并不返回数组,返回布尔值 true/false
,数组的每个元素执行回调函数,如果执行结果全为 true
,every
返回 true
,否则返回 false
。
arr = [1, 3, 5, 7, 8, 9] // false,8为偶数,不满足 arr.every(ele => ele % 2 == 1) arr2 = [2, 4, 6] // true 都是偶数 arr2.every(ele => ele % 2 == 0) 复制代码
模拟实现
Array.prototype.myEvery = function(callbackFn) { var _arr = this, thisArg = arguments[1] || window; // 开始标识值为true // 遇到回调返回false,直接返回false // 如果循环执行完毕,意味着所有回调返回值为true,最终结果为true var flag = true; for (var i = 0; i<_arr.length; i++) { // 回调函数执行为false,函数中断 if (!callbackFn.call(thisArg, _arr[i], i, _arr)) { return false; } } return flag; } 复制代码
some
用法
some
与 every
功能类似,都是返回布尔值。只要回调函数结果有一个 true
,some
便返回 true
,否则返回 false
。
模拟实现
Array.prototype.mySome = function(callbackFn) { var _arr = this, thisArg = arguments[1] || window; // 开始标识值为false // 遇到回调返回true,直接返回true // 如果循环执行完毕,意味着所有回调返回值为false,最终结果为false var flag = false; for (var i = 0; i<_arr.length; i++) { // 回调函数执行为false,函数中断 if (callbackFn.call(thisArg, _arr[i], i, _arr)) { return true; } } return flag; } 复制代码
find/findIndex
用法
find
与 findIndex
是 ES6
新添加的数组方法,返回满足回调函数的第一个数组元素/数组元素索引。当数组中没有能满足回调函数的元素时,会分别返回 undefined和-1
。
const users = [ { name: 'John', age: 34 }, { name: 'Amy', age: 20 }, { name: 'camperCat', age: 10 } ]; 复制代码
- 返回name为John的年龄
在没有find方法时,实现类似效果,需要循环遍历,查找到name=Jonn后,找到年龄。但使用find就可以轻松快捷的实现。
JohnAge = users.find(user => user.name === 'John').age 复制代码
- 返回name为Amy的索引
ES6以前Array提供了查找数组中元素的方法:indexOf,lastIndexOf,但是这两个方法在查找对象时都无能为力。
// 返回值为-1,说明未查到Amy users.indexOf({ name: 'Amy', age: 20 }) // 返回hi为1,成功查到Amy users.findIndex(user => user.name === 'Amy') 复制代码
indexOf也可以用来查找数组中是否存在某元素,但其语义化并不好,每次需要与 -1 进行比较,因此 ES6 添加了新的 includes 方法。
模拟实现
find/findIndex
都是寻找到第一个满足回调函数的元素返回,上面我们学习的 some
也是类似机制,因此它们的原生代码类似。
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
用法
reduce
与前面的方法有略微的差别:
arr.reduce(callback(accumulator, currentValue[, index[, array]]){ }[, initialValue]) 复制代码
callback
:对数组元素进行操作的回调函数
accumulator
:累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue
currentValue
:正在处理的当前元素- 当前元素的索引
- 调用高阶函数的数组
initialValue
:作为第一次调用函数的初始值。如果没有提供初始值,则使用数组中的第一个元素。
没有初始值的空数组调用 reduce 会报错
可累加的效果为 reduce 增添了很多精彩,也产生了很多很有用的用途。
- 数组累加和
arr = [0, 1, 2, 3, 4] arr.reduce((accu, current) => accu + current, 0) 复制代码
- 累加对象数组和
这里如果只是像上面一样使用reduce,最终的结果会存在问题
objArr = [{x: 1}, {x:2}, {x:3}] objArr.reduce((accu, current) => accu.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; } 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; } 复制代码
往期精彩文章
- 牛客最新前端JS笔试百题
- JavaScript之彻底理解原型与原型链
- JavaScript之预编译学习
- JavaScript之彻底理解EventLoop
- 《2w字大章 38道面试题》彻底理清JS中this指向问题