JS基础-2|数组方法,原生JS实现ES5、ES6及之后版本部分数组方法

111 阅读5分钟

在实际开发过程中,数组是最常见的数据结构,同时在开发中,也会有一部分时间用于数组处理。原生JS提供了一部分的方法用于处理数组数据,可以处理一部分业务情况,但是这些方法对一些情况处理起来还是比较复杂,所以,在早期的时候,前端常用的方案都是封装基础方法库或在prototype上挂载方法来满足业务需要。现在ES5新增了部分数组方法,一起来看一看。
下属标注❗️的为经常使用的方法,需要重点关注,未标注的不代表不使用,仅使用频率低

关于数组的全部方法请查阅W3C的JavaScript 数组参考手册或MDN的Array对象

原生数组方法

原生JS提供了以下数组方法:

  • toString 把数组转换为字符串,不可指定分隔符,返回字符串,不改变原数组
  • join 把数组转为字符串,可以指定分隔符,返回字符串,不改变原数组
  • pop 删除数组最后一个元素,返回被删除的元素, 改变原数组
  • push 向数组添加一个新的元素,返回新数组长度,改变原数组
  • shift 删除首个数组元素,返回被删除的元素,改变原数组
  • unshift 在数组头部添加新元素, 返回新数组的长度,改变原数组
  • delete 删除数组元素,注意数组元素不会位移,改变原数组
  • splice 向数组添加新元素,此方法可删除、添加、修改元素,且数组会出现位移变化,返回已删除项的数组,改变原数组
  • concat 合并数组,返回一个新数组,不改变原数组
  • slice 裁剪数组,返回裁剪后的数组,不改变原数组
  • sort 数组排序,返回排序后的数组,改变原数组
  • reverse 反转数组,返回反转后的数组,改变原数组

总结:

  1. 会改变原数组的方法(8个):pop、push、shift、unshift、delete、splice、sort、reverse
  2. 不会改变原数组的方法(4个):toString、join、concat、slice
  3. 支持数组增删改的方法:splice
  4. 返回数组长度的方法:push、unshift(添加元素且修改原数组会返回新数组长度)

ES5新增数组方法

ES5新增了部分数组方法便于更方便处理数组数据

  • ❗️ isArray 检测是否为数组,(上一章封装函数有相关类型检测方法)
  • ❗️ forEach 数组迭代方法,操作原数组
  • ❗️ map 数组迭代方法,返回新数组,不会更改原始数组
  • ❗️ filter 数组迭代方法,过滤符合条件的元素,返回新数组,不会更改原始数组
  • ❗️ reduce 数组迭代方法,为每个元素执行回调,返回新数组,不会更改原始数组
  • ❗️ reduceRight 和reduce一样,只不过是从右向左。返回新数组,不会更改原始数组
  • every 检查所有元素是否通过测试,返回true/false
  • some 检查某些数组值是否通过了测试,返回true/false
  • indexOf 检索值在数组中首次出现的位置,返回位置下标
  • lastIndexOf 检索值在数组中末次出现的位置,返回位置下标

ES5新增了10个数组方法,包含检测类型(1个)、迭代器(5个)、元素检测(2个)、查找下标(2个)。

上述方法其实基于原生JS完全能实现,ES5的提供算是锦上添花。博客下半篇将原生JS实现上述几个方法。

上述的forEach方法不支持跳出循环,因此,根据实际业务需要选择for循环和forEach迭代器。

ES6以后新增数组方法

ES6新增的数组方法:

  • from 将两类对象转为真正的数组
  • of 将一组值,转换为数组
  • copyWithin
  • fill
  • entries
  • keys
  • values
  • ❗️ find 返回通过测试函数的第一个数组元素的值
  • ❗️ findIndex 返回通过测试函数的第一个数组元素的索引

ES2016新增的数组方法

  • ❗️ includes

ES2019新增的数组方法

  • ❗️ flat
  • ❗️ flatMap

注意,ES6是2015年发布,以后版本均已ES+年份命名。参考JavaScript历史
在阮老师的ES6中把后续几个方法都纳入进去了,并非ES6不支持,而是未纳入标准中。原型已有相应方法(例如Chrome会提前支持预发布的标准)

原生JS实现ES5中的方法

虽然ES6及ES2016添加了几个方法供开发使用,那没有这些方法之前是怎么做的呢?下面用原生JS实现一下新增的数组方法

export function isArray (data) {
  return Object.prototype.toString.call(data) === '[object Array]'
}

export function forEach(data, callback) {
  for (var i = 0; i < data.length; i++) {
    callback(data[i], i)
  }
}

export function map(data, callback) {
  var result = [];
  for (var i = 0; i < data.length; i++) {
    result.push(callback(data[i], i))
  }
  return result;
}

export function filter(data, callback) {
  var res = [];
  for (var i = 0; i < data.length; i++) {
    if (callback(data[i])) {
      res.push(data[i])
    }
  }
  return res;
}

export function reduce(data, callback, res) {
  var result = typeof res === 'undefined' ? data[0] : res;
  for (var i = 0; i < data.length; i++) {
    result = callback(data[i], result)
  }
  return result;
}

export function reduceRight(data, callback, res) {
  var result = typeof res === 'undefined' ? data[0] : res;
  for (var i = data.length - 1; i >= 0; i--) {
    result = callback(data[i], result)
  }
  return result;
}

export function every(data, callback) {
  var result = true;
  for (var i = 0; i < data.length; i++) {
    if (!callback(data[i])) {
      result = false;
      break;
    }
  }
  return result;
}

export function some(data, callback) {
  var result = false;
  for (var i = 0; i < data.length; i++) {
    if (callback(data[i])) {
      result = true;
      break;
    }
  }
  return result;
}

export function indexOf(data, callback) {
  var result = -1;
  for (var i = 0; i < data.length; i++) {
    if (callback(data[i])) {
      result = i;
      break;
    }
  }
  return result;
}

export function lastIndexOf(data, callback) {
  var result = -1;
  for (var i = data.length - 1; i >= 0; i--) {
    if (callback(data[i])) {
      result = i;
      break;
    }
  }
  return result;
}

export function find(data, callback) {
  var result = undefined;
  for (var i = 0; i < data.length; i++) {
    if (callback(data[i])) {
      result = data[i];
      break;
    }
  }
  return result;
}

export function findIndex(data, callback) {
  var result = -1;
  for (var i = 0; i < data.length; i++) {
    if (callback(data[i])) {
      result = i;
      break;
    }
  }
  return result;
}

export function includes(data, value) {
  var result = false;
  for (var i = 0; i < data.length; i++) {
    if (data[i] === value) {
      result = true;
      break;
    }
  }
  return result;
}

export function flat(data, deep) {
  if (!deep) deep = 1;
  var result = [];
  (function deepEach(arr, deepNum) {
    for (var i = 0; i < arr.length; i++) {
      if (isArray(arr[i]) && deepNum <= deep) {
        deepEach(arr[i], deepNum + 1)
      } else {
        result.push(arr[i])
      }
    }
  })(data, 1)
  return result
}

上述代码用原生JS实现了部分ES5、ES6及后续版本方法,使用了ES6的export进行了导出,原生JS使用prototype挂载原型方法即可,将data参数改为this,比如将includes方法挂载在原型上:

Array.prototype.includes = function (value) {
  var result = false;
  for (var i = 0; i < this.length; i++) {
    if (this[i] === value) {
      result = true;
      break;
    }
  }
  return result;
}

注:Github源码地址

相关试题

列举部分试题供参考,答案都在上述博文中。

  1. 删除数组指定元素的方法?
  2. 截取指定长度的数组方法?
  3. 如何快速实现数组元素去重?
  4. 你知道有哪些数组迭代器吗?
  5. forEach和for循环有什么区别?
  6. ES6数组新增了哪些特性?
  7. 要删除数组元素但不改变原数组该怎么做?
  8. 原生JS实现数组的reduce方法(笔试)
  9. 原生JS实现数组的flat方法(笔试)