数组操作

348 阅读9分钟

图片来源: Unsplash

很多情况下在工作中常常会使用到数组的操作方法, 特别有时容易对增删改查那几个简单的方法都会造成困扰

检测是否为数组

/** ES6  **/
const arr = [];

Array.isArray(arr); // true
Array.isArray(false); // false
Array.isArray(1); // false
Array.isArray('string'); // false

/** ES5  **/
function isArray(arr) {
  return Object.prototype.toString.call(arr) === '[object Array]';
}

length

一个 0 到 Math.pow(2, 32)-1 的整数,可以通过 length 属性对数组进行截取和添加,如果传入的数值不是有效的数组长度支持的数值,则会抛出异常

const a = [];
a.length; // 0
a.length = 1; // 会在a数组中添加一个元素,该元素的值为 undefined
a.push(1, 2, 3);
a.length; // 3
a.length = 2; // 1, 2

Methods

ES6新增:

  • Array.from: 将一个类数组,伪数组转换为真正的数组,例如获取到元素集合

  • Array.of: 创建一个 可变数量参数的新数组实例,而不类似于Array()方法

Array.of(2); // [2]
Array(2); // [, ,] 两个由undefined 组成的数组

push

**MDN**在数组的末尾添加新的元素,并返回 新的长度,也可以调用函数的callapply方法对伪数组操作数组的方法

const a = [1, 2];
a.push(3); // 可以添加多个元素

a.length === 3; // true

a.push(4, 5);

a.length === 5; // true

pop

**MDN**删除数组中的最后一个元素,并返回删除的元素,如果该数组为空,则返回 undefined

const a = [1, 2, 3, 4, 5];
a.pop(); // 5
console.log(a); // [1, 2, 3, 4]

shift

**MDN**删除数组中下标为0的元素,并返回该元素,会修改元素组,如果数组为空,则返回undefined

const a = [1, 2, 3, 4, 5];

a.shift(); // 1

a.length === 4; // true

unshift

**MDN**在数组下标为0的元素前添加一个新的元素,并返回新的长度

const a = [2, 3, 4, 5];

a.length === 4; // true

a.unshift(1); // [1, 2, 3, 4, 5]

a.length === 5; // true

fill

**MDN**用一个固定值填充数组从起始位置到结束位置中间所有的值,意思也就是替换掉中间的值,传参arr.fill(value, start, end) value: 值, start: 开始位置, end: 结束位置,如果传入的start 或者 end 为负数,则使用数组的长度和其相加的位置计算start + lengthend + length, 不包含 end的位置

const a = [1, 2, 3, 4, 5];
a.fill(6); // [6, 6, 6, 6, 6]
a.fill(7, 4); // [6, 6, 6, 6, 7]
a.fill(1, 2, 3); // [6, 6, 1, 6, 7]
a.fill(2, -4, -2); // [6, 2, 2, 6, 7]

sort

对数组中的元素通过某种规则进行排序,并返回数组,使用该方法排序不一定是稳定的, 默认的排序方法是根据Unicode 编码排序, 该方法接收一个参数,就是一个排序规则的函数

const a = [1, 20, 100, 4];
a.sort(); // [1, 100, 20, 4], 因为在 unicode 中, 100 在 20 前, 20 在4 前
a.sort(function(a, b) {
  return a - b;
}); // [1, 4, 20, 100] 升序排序

a.sort(function(a, b) {
  return b - a;
}); // [100, 20, 4, 1] 降序排序

reverse

对数组中的元素进行颠倒排序,并返回数组

const a = [1, 2, 3, 4, 5];
a.reverse(); // [5, 4, 3, 2, 1]

includes

判断数组中是否包含某一个元素,返回Boolean值, 可接收第二参数arr.includes(ele, fromIndex), fromIndex: 开始位置查找

const a = [1, 2, 3];

a.includes(1); // true

a.includes(4); // false

indexOf

**MDN**查找数组中某一个元素的下标值,若数组中不包含该元素,则返回 -1,可用于判断数组中是否包含某一个元素, 可接收第二参数作为开始位置进行查找arr.indexOf(ele, fromIndex), 该方法使用严格相等判断

const a = [1, 2, 3];
a.indexOf(1); // 0
a.indexOf(4); // -1
a.indexOf(1, 4); // -1

lastIndexOf

**MDN**和 indexOf方法类似,此方法是从数组末尾向前开始查找,接收第二参数作为开始位置arr.lastIndexOf(ele, fromIndex)

var array = [2, 5, 9, 2];
var index = array.lastIndexOf(2);
// index is 3
index = array.lastIndexOf(7);
// index is -1
index = array.lastIndexOf(2, 3);
// index is 3
index = array.lastIndexOf(2, 2);
// index is 0
index = array.lastIndexOf(2, -2);
// index is 0
index = array.lastIndexOf(2, -1);
// index is 3

find

**MDN**查找数组中符合条件的元素,并返回第一个符合条件的元素,接收一个函数作为条件判断

const a = [1, 2, 3, 4, 5];

a.find(function(x) {
  return x % 2 === 0;
}); // 2

findIndex

**MDN**查找数组中符合条件的元素的下标值,返回该元素在数组中的下标,接收一个函数作为条件判断arr.findIndex(function(ele, index, array) {}) ele: 循环当前元素, index: 当前元素的下标, array: 数组本身

const a = [1, 2, 3, 4, 5];

a.findIndex(function(x) {
  return x % 2 === 0;
}); // 1

forEach

**MDN**对数组中的每一个元素进行函数调用, 返回undefined, 可用于计算,函数事件绑定

const a = [1, 2, 3, 4, 5];

a.forEach(function(x, index) {
  console.log(x); // 1, 2, 3, 4, 5
  console.log(index); // 0, 1, 2, 3, 4
});

const b = document.querySelectorAll('div');
function bindDiv(x) {
  console.log(x);
}
b.forEach(function(ele) {
  ele.onclick = bindDiv.bind(ele);
});

map

**MDN**对数组进行迭代, 返回经过计算之后的数组, 不修改原数组

const a = [1, 2, 3];
a.map(function(x) {
  return x + 1;
}); // [2, 3, 4]

filter

**MDN**对数组进行迭代,并将符合条件,返回true的元素作为一个数组返回,不修改原数组

const a = [1, 2, 3, 4, 5];

a.filter(function(x) {
  return x % 2 === 0;
}); // [2, 4]

reduce

**MDN**将数组迭代,并按照接收的函数作为计算条件,返回计算后的结果,可接收第二参数作为默认值

const a = [1, 2, 3, 4, 5];

a.reduce(function(x, y) {
  return x + y;
}, 0); // 15

const b = [
  {
    age: 1,
  },
  {
    age: 2,
  },
];

b.reduce(
  function(x, y) {
    return { age: x.age + y.age };
  },
  { age: 0 },
); // { age: 3 }

reduceRight

**MDN**和 reduce 方法类似,该方法是从后向前进行迭代计算

const a = [1, 2, 3, 4, 5];

a.reduce(function(x, y) {
  return x + y;
}, ''); // '12345'

a.reduceRight(function(x, y) {
  return x + y;
}, ''); // '54321'

every

**MDN**检测是否所有的数组元素都符合传入条件,接收 函数 作为条件判断

const a = [1, 2, 3, 4, 5];

a.every(function(x) {
  return x % 2 === 0;
}); // false

some

**MDN**和 every 方法类似,检测 数组中是否有符合条件的元素,返回 Boolean, 只要有一个元素符合 则退出检测

const a = [1, 2, 3, 4, 5];

a.some(function(x) {
  return x % 2 === 0;
}); // true

copyWithin

**MDN**浅复制数组的一部分到同一数组中的另一个位置,并返回它,而不修改其大小。

join

将数组中的元素使用某一个字符进行拼接,得到一个string,默认值为,,如果数组元素为undefined 或者 null 则会转化为"" 空字符串

const a = [1, 2, 3];

a.join(); // 1, 2, 3

a.join('-'); // 1-2-3

concat

**MDN**将两个或者多个数组进行合并,不会修改原数组,返回一个新的数组

const a = [1, 2, 3];

const b = [4, 5];

const c = a.concat(b);

c; // [1, 2, 3, 4, 5]

slice

**MDN**将一个数组进行浅拷贝,返回一个新的数组,不修改原数组,可接收两个参数arr.slice(start, end),将拷贝从start(包含start位置) 到end(不包含end)结束位置的元素, 如果只有一个参数,则默认结束end 为数组长度减 1(arr.length - 1),

const a = [1, 2, 3, 4, 5];

a.slice(0, 2); // [1, 2]

splice

**MDN**删除元素, 替换元素,可接受三个参数arr.splice(index, num, replace) index: 删除元素的下标开始位置, num: 删除元素的个数, replace: 替换删除掉的元素, 返回删除的元素,若删除 0 个元素,返回空数组, 此方法会修改原数组

const a = [1, 2, 3, 4, 5];

a.splice(0, 1, '聂晓飞');

新增

flat

MDN 方法会按照一个可指定的深度递归遍历数组, 并将所有元素与遍历到的子数组中的元素合并为一个新数组返回, 可以接收一个参数, 表示为递归的层级, 默认 1, 如果嵌套层级较深, 可以使用第二个指定的层级参数

const a = [1, 2, 3, [4, 5]];

// 在 a 变量中保存了三个单元素 和一个 数组元素, 如何把合并为5个单元素
// 其实就这么简单
const b = a.flat(); // [1, 2, 3, 4, 5]
  • 假设放在没有提供这种方法情况下怎么实现呢
    • 简单的话可以使用 concat(合并一个或者多个数组, 不会修改原数组)进行拼接, 但只能用于一维数组的合并
function flatSingle(arr) {
  return [].concat(...arr);
}

flatSingle(a); // [1, 2, 3, 4, 5]
  • 操作多维数组或者不知嵌套层级的数组呢, 可以使用高阶函数 reduce 进行实现
const t = [1, 2, 3, [4, 5, [6, 7, [8, 9]]]];
function flat(arr) {
  return arr.reduce((prev, next) => (Array.isArray(next) ? prev.concat(flat(next)) : prev.concat(next)), []);
}

flat(t); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

当然还有很多种方法可以实现, 更多参考, 无论用什么样的方法实现, 我们需要的都是更少的代码实现更多的功能, 还需要易于维护和修复问题, 也可以使用 for 循环(考虑也是需要递归, 只是二维数组的话, 直接遍历就好)

flatMap

**MDN**首先使用映射函数映射每个元素, 然后将结果压缩成一个新数组, 与 map 和深度值 1 的 flat 几乎相同, 但 flatMap 通常在合并成一种方法的效率更高一点, 接收一个回调函数, 回调函数有三个参数 和 map 一样

  • currentValue: 当前遍历的值
  • index: 当前遍历的下标
  • array: 当前遍历的数组
  • 接收第二个参数: thisArg
const arr = [1, 2, 3, 4, 5];

arr.flatMap((item) => item * 2); // 返回新数组 [2, 4, 6, 8, 10], 感觉和 map 没什么区别嘛

例如: 示例中还展示了和 map 的不同之处

let arr = ['今天天气不错', '', '早上好'];

arr.map((s) => s.split('')); // 只是单纯的遍历, 并没有将遍历得到的值进行合并
// [["今", "天", "天", "气", "不", "错"],[],["早", "上", "好"]]

// 遍历数组, 并对其进行合并为一维数组
arr.flatMap((s) => s.split(''));
// ["今", "天", "天", "气", "不", "错", "早", "上", "好"]

再如: 对树形结构的值进行合并 测试数据

// 此种方法只能是如 flat 递归层级为 1时的合并数组, 不能把子集的再次进行合并, 可以再次进行合并
function flatTreeData(arr) {
  return arr.flatMap((item) => item.children);
}

// todo: 这是一个失败的测试, 得到的数组长度是对的, 但每一项都是 undefined, 至少层级为1时可以这么搞
function flatTreeDataDeep(arr) {
  return arr.flatMap((item) =>
    Array.isArray(item.children) ? [].concat(flatTreeDataDeep(item.children)) : item.children,
  );
}

// 伪代码
function flatTree(array) {
  return array.reduce((prev, next) => {
    if (Array.isArray(next.children)) {
      prev.push(flatTree(next.children));
      return prev;
    }
    prev.push(next);
    return prev;
  }, []);
}

总结

  • 修改原数组长度的方法: push, pop, shift, unshift
  • 修改原数组的方法: sort, reverse, fill
  • 遍历方法: forEach, every, some, filter
  • 查找方法: includes, find, findIndex, indexOf, lastIndexOf
  • 其他操作方法: concat, join
  • 转换方法: of, from
  • 数组去重新方法: [...new Set(arr)]
  • 高阶函数: flat faltMap map reduce filter
// @TODO 希望能整理更多关于数组中的某些业务操作