当前位置:首页 > 技术分析 > 正文内容

半小时,阿包带你学会手撕高阶函数

ruisui885个月前 (02-03)技术分析21

前言

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 }
];
复制代码
  1. 返回name为John的年龄
  2. 在没有find方法时,实现类似效果,需要循环遍历,查找到name=Jonn后,找到年龄。但使用find就可以轻松快捷的实现。
  3. JohnAge = users.find(user => user.name === 'John').age 复制代码
  4. 返回name为Amy的索引
  5. ES6以前Array提供了查找数组中元素的方法:indexOf,lastIndexOf,但是这两个方法在查找对象时都无能为力。
  6. // 返回值为-1,说明未查到Amy users.indexOf({ name: 'Amy', age: 20 }) // 返回hi为1,成功查到Amy users.findIndex(user => user.name === 'Amy') 复制代码
  7. 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;
}

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/686.html

分享给朋友:

“半小时,阿包带你学会手撕高阶函数” 的相关文章

Ubuntu 24.10发行版登场:Linux 6.11内核、GNOME 47桌面环境

IT之家 10 月 11 日消息,Canonical 昨日发布新闻稿,正式推出代号为 Oracular Oriole 的 Ubuntu 24.10 发行版。新版在内核方面升级到最新 6.11 版本,并采用 GNOME 47 桌面环境。Ubuntu 24.10 发行版调整了内核策略,开始选择最新的上游...

身体越柔软越好?刻苦拉伸可能反而不健康 | 果断练

坐下伸直膝盖,双手用力向前伸,再用力……比昨天前进了一厘米,又进步了! 这么努力地拉伸,每个人都有自己的目标,也许是身体健康、线条柔美、放松肌肉、体测满分,也可能为了随时劈个叉,享受一片惊呼。 不过,身体柔软,可以享受到灵活的福利,也可能付出不稳定的代价,并不是越刻苦拉伸越好。太硬或者太软,都不安全...

30 个纯 HTML5 实现的游戏

浏览器和 JavaScript 的功能逐年不断的变强变大。曾几何时,任何类型的游戏都需要Flash。但随着 HTML5 发展,HTML5 + WebGL 游戏式就慢慢占领着这个舞台。以下是30款流行的游戏,它们可以在所有现代浏览器中运行,并且只使用web技术构建。1. HexGL地址:http://...

HTML5+眼球追踪?黑科技颠覆传统手机体验

今天,iH5工具推出一个新的神秘功能——眼动追踪,可以通过摄像头捕捉观众眼球活动!为了给大家具体演示该功能的使用,我做了一个案例,供大家参考。实际效果如下:案例比较简单,就是通过眼动功能获取视觉焦点位置,剔除用户看中的牌。现在,舞台的属性中多了一个“启用眼动”的选项,另外,还多了一个“启用摄像头”的...

Solid State Logic 发布低保真数字失真插件 Digicrush

Solid State Logic 宣布推出低保真数字失真插件 Digicrush ,他们最新的创意工具具有经典数字失真的粗糙、低保真特性,完美模拟早期数字音频的衰减和伪影。Digicrush 充满怀旧气息,深受经典数字采样器和效果器的影响,具有内置抖动、可调比特深度和采样率降低功能,是为音轨添加复...

三维家-系统快捷键使用

快键件使用:通过简单的键盘+鼠标操作,快速完成搭配。1.基础快捷键1) Ctrl+V:复制选中对象第一步:鼠标左击物体,按下Ctrl+V 即可复制选中对象。2) Ctrl+G:组合多选对象第一步:按住Ctrl键多选对象--按住Ctrl+G--确定。3) Ctrl+B:解组选中对象第一步:左击选中对象...