【手撕JS高阶函数】

前言


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 并不是返回数组,而是返回布尔值,数组的每个元素执行回调函数,如果执行结果都是 trueevery 返回 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 并不是返回数组,而是返回布尔值,数组的每个元素执行回调函数,如果遇到执行结果是 truesome 返回 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


用法

findfindIndexES6 新添加的数组方法,返回满足回调函数的第一个数组元素/数组元素索引。当数组中没有满足回调函数的元素时,分别返回 undefined-1

  const usersInfo = [
    {name: 'jack', age: 24},
    {name: 'lucy', age: 21},
    {name: 'nikky', age: 30}
  ]
  1. 返回name为lucy的年龄
    在没有find方法的时候,实现类似的效果需要遍历数组,查到name=jacy后再找到年龄,使用find就方便很多:
  const jacyAge = usersInfo.find(item => item.name === 'jacy').age;
  1. 返回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 会报错。

  1. 数组累加和
arr = [0, 1, 2, 3, 4]
arr.reduce((prev, current) => prev + current, 0)
  1. 累加对象数组和
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)
  1. 计算数组中每个元素出现的次数
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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

small_Axe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值