前端高频面试题--数组上常用的方法

76 阅读4分钟

前言:数组到底有多重要?

先问个问题:你写的代码里,有多少时间是在跟数组打交道?我敢说至少30%!不管是处理接口数据、操作DOM列表,还是做各种计算,数组都是我们的好基友。

今天我就把数组那点事儿给你掰扯明白,从创建到遍历,一个不漏!

1. 创建数组:别只会用 [] 了!

1.1 ES6 Array.of():专治各种不服

先看个坑:

// 传统new Array的坑
new Array(3)        // [empty × 3]  🤔
new Array(3, 4)     // [3, 4]      😅

// Array.of的稳
Array.of(3)         // [3]         👍
Array.of(3, 4)      // [3, 4]      👍

Array.of() 就是个老实人,你传什么它就给你什么,从不耍花招。

1.2 ES6 Array.from():把"像数组的"变成真数组

有些东西看起来像数组,但用起来却不是那个味儿:

// 常见的类数组对象
const nodeList = document.querySelectorAll('div'); // NodeList
const argumentsObj = function() { return arguments; }(1, 2, 3); // Arguments

// 以前要这样转
const oldWay = Array.prototype.slice.call(nodeList);

// 现在爽多了
const newWay = Array.from(nodeList);

更厉害的是,它还能玩映射:

// 一键生成序列数组
Array.from({ length: 5 }, (_, index) => index * 2);
// [0, 2, 4, 6, 8]  爽不爽?

2. 方法大全:改变VS不改变,别搞混了!

2.1 改变原数组的"狠角色"(9个)

这些方法用的时候要小心,它们会直接修改原数组!

splice():数组的"瑞士军刀"

const fruits = ['苹果', '香蕉', '橙子'];

// 删除:从索引1开始删除1个元素
fruits.splice(1, 1); // 返回 ['香蕉'],fruits变成 ['苹果', '橙子']

// 添加:从索引1开始添加
fruits.splice(1, 0, '葡萄'); // fruits变成 ['苹果', '葡萄', '橙子']

// 替换:删除并添加
fruits.splice(1, 1, '芒果'); // fruits变成 ['苹果', '芒果', '橙子']

sort():排序的坑你踩过吗?

const numbers = [10, 2, 1, 20];

// 直接排序会按字符串比较!
numbers.sort(); // [1, 10, 2, 20]  😱

// 正确姿势
numbers.sort((a, b) => a - b); // [1, 2, 10, 20]  👍

pop() & shift():头尾删除兄弟

const arr = [1, 2, 3];

arr.pop();   // 返回3,arr变成[1, 2]
arr.shift(); // 返回1,arr变成[2]

push() & unshift():头尾添加兄弟

const arr = [2];

arr.push(3);    // arr变成[2, 3],返回新长度2
arr.unshift(1); // arr变成[1, 2, 3],返回新长度3

reverse():翻转很简单

[1, 2, 3].reverse(); // [3, 2, 1]

ES6 copyWithin():内部拷贝

这个有点绕,但用对了很爽:

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

// 把索引3-4的元素复制到索引0的位置
arr.copyWithin(0, 3, 5); // [4, 5, 3, 4, 5]

ES6 fill():批量填充

// 创建长度为5,全是0的数组
const zeros = new Array(5).fill(0); // [0, 0, 0, 0, 0]

// 替换部分元素
[1, 2, 3, 4].fill('x', 1, 3); // [1, 'x', 'x', 4]

2.2 不改变原数组的"老实人"(8个)

这些方法会返回新数组或新值,原数组纹丝不动。

slice():浅拷贝小能手

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

arr.slice(1, 3);    // [2, 3] - 包左不包右
arr.slice(1);       // [2, 3, 4, 5] - 从1到结束
arr.slice(-2);      // [4, 5] - 倒数两个

join():数组转字符串

['a', 'b', 'c'].join();      // "a,b,c"
['a', 'b', 'c'].join('-');   // "a-b-c"
['a', 'b', 'c'].join('');    // "abc"

concat():数组合并

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

indexOf() & lastIndexOf():找位置

const arr = [1, 2, 3, 2, 1];

arr.indexOf(2);     // 1 - 第一个2的位置
arr.lastIndexOf(2); // 3 - 最后一个2的位置
arr.indexOf(4);     // -1 - 找不到

ES7 includes():是否存在

const arr = [1, 2, 3, NaN];

arr.includes(2);  // true
arr.includes(4);  // false
arr.includes(NaN); // true - 这个比indexOf强!

2.3 遍历方法:12个方法让你玩转数组

forEach():最常用的遍历

[1, 2, 3].forEach((item, index) => {
  console.log(`索引${index}的值是${item}`);
});

every() & some():条件检查

// every:所有元素都要满足条件
[2, 4, 6].every(x => x % 2 === 0); // true

// some:至少一个满足条件
[1, 2, 3].some(x => x % 2 === 0);  // true

filter():过滤高手

const numbers = [1, 2, 3, 4, 5, 6];

// 找出所有偶数
const evens = numbers.filter(x => x % 2 === 0); // [2, 4, 6]

map():数据转换

const users = [
  { name: '小明', age: 18 },
  { name: '小红', age: 20 }
];

// 提取姓名数组
const names = users.map(user => user.name); // ['小明', '小红']

reduce():最强大的方法

这个有点难,但学会了是真香!

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

// 求和
const sum = numbers.reduce((total, num) => total + num, 0); // 15

// 统计字符出现次数
const chars = ['a', 'b', 'a', 'c', 'b', 'a'];
const count = chars.reduce((obj, char) => {
  obj[char] = (obj[char] || 0) + 1;
  return obj;
}, {}); // {a: 3, b: 2, c: 1}

ES6 find() & findIndex():按条件查找

const users = [
  { id: 1, name: '小明' },
  { id: 2, name: '小红' }
];

users.find(user => user.id === 2);     // {id: 2, name: '小红'}
users.findIndex(user => user.id === 2); // 1

ES6 keys() & values() & entries():遍历键值

const arr = ['a', 'b', 'c'];

// 遍历索引
[...arr.keys()];     // [0, 1, 2]

// 遍历值(跟数组本身差不多)
[...arr.values()];   // ['a', 'b', 'c']

// 遍历键值对
[...arr.entries()];  // [[0, 'a'], [1, 'b'], [2, 'c']]

实战技巧:这些组合拳让你代码更优雅

链式调用:函数式编程的魅力

const result = [1, 2, 3, 4, 5, 6]
  .filter(x => x % 2 === 0)    // [2, 4, 6]
  .map(x => x * 2)             // [4, 8, 12]
  .reduce((sum, x) => sum + x, 0); // 24

数组去重的N种方法

// 方法1:Set(最简洁)
[...new Set([1, 2, 2, 3, 3])]; // [1, 2, 3]

// 方法2:filter + indexOf
[1, 2, 2, 3, 3].filter((item, index, arr) => 
  arr.indexOf(item) === index
);

// 方法3:reduce
[1, 2, 2, 3, 3].reduce((unique, item) => 
  unique.includes(item) ? unique : [...unique, item], []
);

性能小贴士

  1. 大数据量:避免在循环中修改数组
  2. 查找操作includes 比 indexOf 更语义化
  3. 删除元素:知道长度时用 length = n 最快
  4. 清空数组arr.length = 0 比 arr = [] 更好

总结

数组方法虽多,但记住这个分类就不乱了:

  • 改变原数组:splice、sort、pop、shift、push、unshift、reverse、copyWithin、fill
  • 返回新数组:slice、concat、filter、map
  • 返回其他值:join、indexOf、includes、reduce、find

记住:知道用什么方法比记住所有方法更重要