判断数组应使用Array.isArray()或Object.prototype.toString.call();push、pop、shift、unshift均修改原数组;map/filter/find/some/every语义不同且不改原数组;slice只读,splice修改原数组。
t
ypeof
用 typeof [] 得到的是 "object",完全不可靠。真正安全的判断方式只有两个:Array.isArray() 或 Object.prototype.toString.call()。
Array.isArray([]) → true,这是 ES5+ 标准推荐方法,兼容性好(IE9+)Object.prototype.toString.call([]) === '[object Array]' 适合需要支持极老环境(如 IE6–8)的场景instanceof Array:跨 iframe 时会失效,因为不同上下文的 Array 构造函数不相等push、pop、shift、unshift 这四个方法改不改变原数组?全部都会直接修改原数组,返回值也各不相同——这点容易混淆,尤其 shift 和 unshift 的返回值不是新长度而是被移除/插入的元素本身。
const arr = [1, 2, 3]; arr.push(4); // → 返回 4(新长度是 4),arr 变成 [1, 2, 3, 4] arr.pop(); // → 返回 4,arr 变成 [1, 2, 3] arr.shift(); // → 返回 1,arr 变成 [2, 3] arr.unshift(0); // → 返回 3(新长度),arr 变成 [0, 2, 3]
[...arr, newItem]
const [, ...rest] = arr
push 和 unshift 性能较差(尤其大数组),因为要移动所有后续元素;push 比 unshift 快得多map、filter、find、some、every 这些方法什么时候该用?它们都不修改原数组,但语义和用途差异很大,选错会导致逻辑错误或性能浪费。
map:你要对每个元素做**转换**,且需要一个**等长新数组**(比如 arr.map(x => x * 2))filter:你要**筛选子集**,返回满足条件的元素组成的数组(比如 arr.filter(x => x > 10))find:你只要**第一个匹配项**,且不关心索引(返回值是元素本身,不是索引)some:你只关心“有没有至少一个满足”,返回布尔值,遇到第一个 true 就停止遍历every:你只关心“是否全部满足”,也是布尔值,遇到第一个 false 就停止注意:findIndex 和 indexOf 不同——前者接受函数,后者只支持严格相等;includes 是 indexOf !== -1 的语义糖,但不支持回调。
splice 和 slice 容易搞混?关键看第三个参数slice(start, end) 是纯读取,不改原数组;splice(start, deleteCount, ...items) 是写操作,会修改原数组并返回被删除的元素。
const arr = [1, 2, 3, 4, 5]; arr.slice(1, 3); // → [2, 3],arr 不变 arr.splice(1, 2); // → [2, 3],arr 变成 [1, 4, 5]
splice 第二个参数为 0 时,只插入不删除(类似 push/unshift 的通用版)slice(0) 是浅拷贝数组的常用写法,比 arr.concat() 或 [...arr] 更早被广泛使用splice 一次调用,得配合循环或 filter
实际项目里最常被忽略的是:所有这些方法对稀疏数组(比如 [1, , 3])的行为不一致,map 和 filter 会跳过空位,forEach 也会,但 for...in 会遍历索引属性,for...of 会把空位当作 undefined ——这种细节在处理用户上传的不规则数据时特别容易出 bug。