外观
数组有哪些必须会的方法?
会改原数组的方法要和返回新数组的方法分开记。面试里最常写的是 map、filter、reduce,以及怎么按条件去重。
原理
先看会不会改原数组,再看返回值是新数组、一个元素,还是一个累计值。回调的参数一般是 (当前值, 下标, 原数组)。
不改原数组
map(fn):每一项都经过fn,返回同样长度的新数组。某一项没有返回值时,新数组对应位置是undefined。
js
[1, 2, 3].map((n) => n * 2) // [2, 4, 6]
[1, 2].map((n) => {
if (n > 1) return n
}) // [undefined, 2]filter(fn):只留下fn返回真值的项,返回新数组。长度可能变短。
js
[1, 2, 3, 4].filter((n) => n % 2 === 0) // [2, 4]reduce(fn, 初始值):从左到右收成一个值。fn的第一个参数是上一次的累计值。不传初始值时,第一项当累计值,空数组会抛错。
js
[1, 2, 3].reduce((total, n) => total + n, 0) // 6
[2, 3].reduce((total, n) => total * n) // 6,没有初始值,从 2 开始乘slice(start, end):复制从start到end(不含end)的一段。负数从末尾数。不传参数等于浅拷贝整个数组。
js
const list = [1, 2, 3, 4]
list.slice(1, 3) // [2, 3]
list.slice(-2) // [3, 4]
list.slice() // [1, 2, 3, 4],list 本身不变concat(...items):把当前数组和参数拼成新数组。参数里的数组只展平一层。
js
[1, 2].concat(3, [4, [5]]) // [1, 2, 3, 4, [5]]flat(depth):把嵌套数组展平,默认一层。flat(Infinity)全部展平。
js
[1, [2, [3]]].flat() // [1, 2, [3]]
[1, [2, [3]]].flat(Infinity) // [1, 2, 3]flatMap(fn):先对每一项做map,再把结果展平一层。fn返回数组时,这些元素会接到新数组里,而不是再包一层。
js
[1, 2].flatMap((n) => [n, n * 10]) // [1, 10, 2, 20]原始值去重可以用 Set,它同样不改原数组:
js
const unique = (list) => [...new Set(list)]
unique([1, 1, 2]) // [1, 2]会改原数组
push(...items):末尾追加,返回新的length。
js
const list = [1]
list.push(2, 3) // 3
list // [1, 2, 3]pop():删掉最后一项并返回它。空数组返回undefined。
js
const list = [1, 2, 3]
list.pop() // 3
list // [1, 2]unshift(...items):头部插入,返回新的length。
js
const list = [2, 3]
list.unshift(1) // 3
list // [1, 2, 3]shift():删掉第一项并返回它。后面的项都要前移。
js
const list = [1, 2, 3]
list.shift() // 1
list // [2, 3]splice(start, deleteCount, ...items):从start删掉deleteCount项,并可在同一位置插入items。返回被删掉的数组,不是剩下的原数组。
js
const list = [1, 2, 3, 4]
list.splice(1, 2, 9) // [2, 3]
list // [1, 9, 4]sort(compare):原地排序。不传比较函数时按字符串排,数字要写(a, b) => a - b。返回排好的原数组。
js
const byText = [10, 2, 1]
byText.sort() // [1, 10, 2],按字符串比较
const byNumber = [10, 2, 1]
byNumber.sort((a, b) => a - b) // [1, 2, 10]reverse():原地倒序,返回原数组。
js
const list = [1, 2, 3]
list.reverse() // [3, 2, 1]fill(value, start = 0, end = arr.length):用同一个值填满一段,返回原数组。如果value是对象,每一格都是这个对象的同一引用。
js
[1, 2, 3].fill(0, 1) // [1, 0, 0]
const box = {}
const filled = Array(2).fill(box)
filled[0] === filled[1] // true查找和判断
forEach(fn):逐项执行,返回undefined。不能break,也不会把返回值收成数组。
js
const seen = []
[1, 2].forEach((n) => seen.push(n))
seen // [1, 2]find(fn):返回第一个让fn为真的元素,没有则是undefined。
js
[{ id: 1 }, { id: 2 }].find((item) => item.id === 2) // { id: 2 }
[1, 2].find((n) => n > 9) // undefinedfindIndex(fn):返回这个元素的下标,没有则是-1。
js
[10, 20, 30].findIndex((n) => n > 15) // 1indexOf(value):用严格相等找下标,找不到是-1。NaN永远找不到。
js
[1, 2, 2].indexOf(2) // 1
[NaN].indexOf(NaN) // -1includes(value):是否包含该值,能认出NaN。
js
[1, 2].includes(2) // true
[NaN].includes(NaN) // truesome(fn):有一项为真就返回true,并停下来。
js
[1, 2, 3].some((n) => n > 2) // true
[].some(() => true) // falseevery(fn):全部为真才返回true。空数组上every为真,some为假。
js
[1, 2, 3].every((n) => n > 0) // true
[].every(() => false) // true追问
forEach 和 map 怎么选?
只做副作用用 forEach,它的返回值是 undefined。要得到新数组用 map。两者都不能用 break 中断,要中断用 for 或 some。
稀疏数组会怎样?
map、forEach 会跳过空位。Array(3) 有三个空位,map 之后仍然是空位,不是 undefined 被映射过。
易错点
sort会改原数组,而且默认不是按数字大小。splice返回被删掉的元素,不是剩下的数组。- 对象数组去重不能靠
Set,除非先规定相等规则。
js
// 根据唯一 key(id),**保留第一次出现的对象** 时间复杂度 O (n),速度快,最常用
function uniqueByKey(arr, key) {
const map = new Map();
return arr.filter(item => {
if(map.has(item[key])) {
return false;
}
map.set(item[key], true);
return true;
});
}
// 保留最后一次出现
function uniqueByKeyLast(arr, key) {
let map = new Map();
for(let item of arr) {
map.set(item[key], item);
}
return Array.from(map.values());
}
// some 循环,大数据性能差
function uniqueByReduce(arr, key) {
return arr.reduce((acc, curr) => {
if(!acc.some(item => item[key] === curr[key])) {
acc.push(curr);
}
return acc;
},[]);
}