回顾 ES5新增数组方法 与实现

342 阅读6分钟

2020 春节疫情严重, 宅在家的时间, 就复习一下 ES5 的数组迭代方法吧. 作为一名合格的前端(API)工程师, 理应熟练掌握 ES5 中新增的几种数组迭代方法与其蕴含的思想, 温故而知新,也是一个良好的学习态度嘛.

新增方法

  • forEach
  • map
  • filter
  • some/every
  • reduce/reduceRight
  • indexOf/lastIndexOf

forEach

对数组的每个元素执行一次回调函数

const arr = [1, 2, 3, 4];
arr.forEach((currentItem, index, array) =>
  console.log(currentItem * currentItem)
);
// 1
// 4
// 9
// 16

由于 forEach 没有返回值, 只是单纯遍历数组, 所以它往往只是用来作简单的数组迭代. 那与 for 循环的区别在哪呢, for 循环作为 js 最原始的迭代方法, 逻辑代码的编写会更自由更方便, 而 forEach 把焦点聚焦在了回调函数中, 会让我们更关注业务而不是遍历对象/循环次数, 作为 for 循环的特殊版本, forEach 并不能 使用 break/continue 退出循环, 也不支持 async/await, 本文对此不作深入研究, 具体了解请看 当 async/await 遇上 forEach.下面给出 基于 for 循环实现的 forEach:

Array.prototype.forEach = function(callback) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  for (let i = 0; i < this.length; i++) {
    callback(this[i], i, this);
  }
};

如果不需要使用 break/continue 退出循环, 或是在 forEach 中作异步请求, 与 for 循环 的区别并不大, 但既然有了新的 API, 当然更推荐使用 forEach. forEach 的入参是一个函数, 本质是一个高阶函数, 事实上 ES5 提供的 几个数组新 API 全是高阶函数的形式, 想了解高阶函数的知识, 请移步 高阶函数,你怎么那么漂亮呢!.

map

遍历提供的数组, 并返回传入函数作用于原数组每项的新数组

const arr1 = [1, 2, 3, 4];
const resultArray = arr1.map(
  (currentItem, index, array) => currentItem * currentItem
);
console.log(resultArray);
// [1, 4, 9, 16]

请注意将 forEach 与 map 进行联系与对比, 两者均接受一个函数作为入参, 函数的入参也都是: (当前遍历项, 当前索引值, 数组自身), forEach 返回 undefined, 而 map 返回经过函数运算后的新数组, 所以此时区别出来了, 当我们需要使用返回后的新数组时, 就应该使用 map 而不是 forEach, 而 map 的出现, 也恰恰体现出了函数式编程的思想, 如果每次给 map 运算的 数组都是相同的, 那它一定给你永远一样的答案, 想了解函数式编程, 请跳转到 函数式编程入门教程, 实现一个 map

Array.prototype.map = function(callback) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  const ret = [];
  for (let i = 0; i < this.length; i++) {
    const item = callback(this[i], i, this);
    ret.push(item);
  }
  return ret;
};

filter

遍历提供的数组, 并返回 符合传入函数运算结果为 true 的项

const arr2 = [1, 2, 3, 4, 5, 99, 1000, 9999];
// 找出值大于或等于 1000 的项
const filterResult = arr2.filter((item, index, array) => item >= 1000);
console.log(filterResult);
// [9999]

// 找出索引位置是偶数的项
const filterResult2 = arr2.filter((item, index, array) => index % 2 === 0);
// [1, 3, 5, 1000]

fitler 运用的关键在于[表达式的运算], 表达式运算后的值为 [true] 的项才会被返回, 而 js 有一项神秘技能 -- 隐式转换, 所以请务必掌握 [隐式转换], 这个在 js 中神秘而又古老的特性, 你所忽略的 js 隐式转换.

Array.prototype.filter = function(callback) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  const ret = [];
  for (let i = 0; i < this.length; i++) {
    const item = callback(this[i], i, this);
    if (Boolean(item)) {
      ret.push(this[i]);
    }
  }
  return ret;
};

some/every

some 与 every 可以联系对比起来理解, some 与 every 都是在遍历给定数组后, 返回一个布尔值, 其值由传入的函数运算后得出, some 要求 [至少有 1 个]元素经过传入函数运算为 true, 而 every 则要求 [全部] 元素经过伟入函数运算为 true.

  • some
const arr3 = [1, 2, 3, 4, 5, 6];
// 测试 至少有 1 个元素 符合 >= 6 这个条件
const someTestResult1 = arr3.some(item => item >= 6);
console.log(someTestResult1);
// true

// 测试 至少有 1 个元素 符合 >= 7 这个条件
const someTestResult2 = arr3.some(item => item >= 7);
console.log(someTestResult2);
// false
  • every
const arr4 = [1, 2, 3, 4, 99, 100, 9999];
// 测试 全部元素 符合 > 0 这个条件
const everyTestResult1 = arr4.every(item => item > 0);
console.log(everyTestResult1);
// true

// 测试 全部元素 符合 <100 这个条件
const everyTestResult2 = arr4.every(item => item < 100);
console.log(everyTestResult2);
// false

some 与 every 的出现, 让我们能用更简短的代码实现需求, 最典型的莫过于 在购物车页面 判断用户是否选中了全部商品, const isSelectedAll = goodsList.every(goods => goos.isSelected) 就搞定, 而结算按钮必须要至少选中一个商品才可以点击, const selectedAtLeastOne = goodsList.some(goods => goos.isSelected) 就搞定, 再也不用 for 来 for 去.

some 的实现:

Array.prototype.some = function(callback) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  const ret = true;
  for (let i = 0; i < this.length; i++) {
    const item = callback(this[i], i, this);
    if (Boolean(item)) break;
  }
  return ret;
};

every 的实现:

Array.prototype.every = function(callback) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  let ret = false;
  for (let i = 0; i < this.length; i++) {
    const item = callback(this[i], i, this);
    if (!Boolean(item)) break;
    ret = true;
  }
  return ret;
};

reduce/reduceRight

对数组中的每个元素执行一个给定的 reducer 函数(升序执行),将其结果汇总为单个返回值

const arr5 = [1, 110, 111, 444];
// 求和
const sumary = arr5.reduce(
  (accumulator, currentItem) => accumulator + currentItem
); // accumulator: 累加值 currentItem: 当前值
console.log(sumary);
// 666

请注意 reduce 与其它几个同胞兄弟的不同是在于, 在 currentItem 前还多了个 accumulator (累加值), 累计器累计回调的返回值, 是上一次调用回调时返回的累积值,或 initialValue (初始值). 完整入参是 .reduce((accumulator, currentItem, index, array) => { ... }, initialValue), 鉴于 reduce 的强大特性, 很多业务场景都可以用到, 所以更多实用指南, 请参考 Array.prototype.reduce 实用指南, 下面给出实现:

Array.prototype.reduce = function(callback, initalValue) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  let __init = initalValue,
    i = 0;
  if (__init === undefined) {
    __init = this[0];
    i = 1;
  }
  for (i; i < this.length; i++) {
    __init = callback(__init, this[i], i, this);
  }
  return __init;
};

indexOf/lastIndexOf

返回找到的 第一个/最后一个 元素的索引, 找不到时返回 -1

const arr6 = ['Jalon', 'Galaxy', 'Mega', 'Star', 'Earth'];
const indexResult = arr6.indexOf('Galaxy');
console.log(indexResult);
// 1

这组 方法没什么好说的, 要不, 你也动手实现一下?

Array.prototype.indexOf = function(callback) {
  if (!callback) throw new Error("undefined is not a function");
  if (typeof callback !== "function")
    throw new Error("callback is not a function");
  let _index = -1;
  for (let i = 0; i < this.length; i++) {
    ...你的代码在这儿
  }
  return _index;
};

小结

以上就是 ES5 最经典的数组遍历方法了, 事实上在我们的开发中, 这些新增的方法不仅能让我们能更快更优雅地编写业务代码, 也把我们引向了函数式编程的领域, 在代码简洁的同时也保持极强的可读性, 可谓一石二鸟. 熟悉的读者朋友可以点左上角的小手指点个赞, 不熟悉的朋友就再琢磨琢磨, 毕竟前端(API)工程师要做好, 也不容易. 互勉, 也顺便祝愿疫情快点好转,让中国顺利度过这场劫难, thank you~~~