arr.at(-1) 和 arr[-1] 有什么区别?越界和旧浏览器怎么办?
arr.at(-1) 取最后一项,arr[-1] 找的却是名为 "-1" 的属性,通常是 undefined。这篇讲清 at 的索引换算规则、越界返回值、只读不能赋值,以及在不支持 ES2022 的环境里怎么兜底。
JavaScript · 数组与集合面试题第 1 页,显示第 1–38 题,共找到 38 道完整解析,可继续按分类、标签与关键词缩小范围。
按稳定语义路径排序
arr.at(-1) 取最后一项,arr[-1] 找的却是名为 "-1" 的属性,通常是 undefined。这篇讲清 at 的索引换算规则、越界返回值、只读不能赋值,以及在不支持 ES2022 的环境里怎么兜底。
数组下标从 0 到 length - 1,所以正向遍历写 i < length 正好 length 次。这篇用求和的例子对比 i <= length 和 i < length - 1 的错误结果,讲清什么时候 length - 1 反而是对的,以及边遍历边删除为什么会漏项。
concat 和 [...a, ...b] 合并普通数组结果一样,都只拆一层、都是浅拷贝。差别在边界上:concat 只拆数组,展开能拆任何可迭代对象;concat 保留空位,展开把空位变成 undefined。附 Symbol.isConcatSpreadable 和大数组 push 展开的坑。
copyWithin 把数组里的一段复制到同一个数组的另一个位置,直接改原数组、长度不变、超出末尾的部分被截掉。这篇讲清参数规则、源和目标重叠时为什么不会被覆盖错、自己写循环会错在哪,以及它真正适用的场景。
数组的 keys、values、entries 返回的不是数组,而是迭代器,分别产出下标、值和 [下标, 值]。迭代器像一次性的游标,读完就停在末尾,所以第二次展开是空的。附它和 Object.keys 在稀疏数组上的差别、迭代时改数组的效果和常见用法。
every 问的是有没有反例,空数组没有反例所以是 true;some 问的是有没有一个满足的,空数组一个都没有所以是 false。这篇讲清背后的逻辑、两者的短路行为、空购物车也能提交这类业务 bug,以及回调写成 async 时的坑。
fill(value, start, end) 填的是左闭右开区间,负数先加上 length 再换算,所以 fill(1, -3, -1) 不会碰到最后一项。附越界裁剪、终点小于起点时的结果,以及 fill([]) 让所有格子共用同一个数组的经典坑。
indexOf 拿一个值用严格相等去比,适合找数字、字符串或者手上已有的那个对象引用;findIndex 接收一个判断函数,适合按属性、按条件找。附对象查找返回 -1 的原因、NaN 和空位上的差异,以及 fromIndex、findLastIndex 等相关用法。
flat 默认只展开一层,要全部拍平传 Infinity。flatMap 的结果等同于 map 之后 flat(1),但只遍历一次、不产生中间数组,而且深度固定为 1。附用 flatMap 同时过滤和一对多映射的写法、空位处理和第二个参数的误用。
flatMap 的回调可以返回零项、一项或多项,所以它能在一次遍历里同时做过滤和映射,也能把一条记录展开成多行。这篇讲清它比 map 加 filter 合适的两类场景、返回 null 过滤不掉的坑,以及它是不是真的更快。
Array.from 认两种输入:可迭代对象和类数组(有 length 和数字下标),数组展开只认可迭代对象,所以只有 length 的对象一展开就报 TypeError。附两者都满足时谁优先、缺失下标变成 undefined、第二个参数 mapFn 的用法。
[NaN].includes(NaN) 是 true,[NaN].indexOf(NaN) 是 -1,因为 includes 用 SameValueZero 比较,indexOf 用严格相等。这篇讲清三种相等算法的区别、两者在 +0 和 -0、稀疏数组上的表现,以及找 NaN 下标的正确写法。
map 和 filter 各返回一个新数组,sort 是原地排序、返回的还是同一个数组,所以三步链式调用只新建两个数组。这篇讲清怎么数分配、合并成一个循环后回调顺序会变、reduce 里展开累加器的平方级分配,以及什么时候才值得优化。
instanceof 查的是原型链上有没有当前页面的 Array.prototype,Array.isArray 查的是对象本身是不是数组。iframe 里的数组原型是另一个 Array.prototype,所以 instanceof 返回 false。附伪造原型、改掉原型、TypedArray、Proxy 等情况下两者的结果。
给对象加一个 Symbol.iterator 方法,让它返回一个带 next 的对象,next 每次返回 { value, done },这个对象就能被 for...of 和展开语法使用。附为什么每次要返回新的迭代器、游标放在对象上导致只能遍历一次的坑,以及 return 方法在提前退出时的清理作用。
类数组是有 length 和数字下标、但原型不是 Array.prototype 的对象,arguments 就是典型。它能按下标读、能 for...of,却没有 map、forEach。附三种转换写法、非严格模式下 arguments 和形参联动的坑、箭头函数没有 arguments,以及为什么新代码应该用剩余参数。
reduce 从左往右遍历,reduceRight 从右往左,回调参数都是累加器在前、当前值在后。加法和乘法不受方向影响,字符串拼接、减法、函数组合就会得到不同结果。附用 reduceRight 实现 compose、它和 reverse 再 reduce 的区别,以及空数组时的报错。
在 forEach 回调里追加、删除、改值,哪些会被后续回调看到?讲清开始时固定长度、访问时再读值这两条规则,以及 splice 删除导致跳项的原因和安全的改法。
sort 不传比较函数时会把元素转成字符串再比较,所以 10 排在 9 前面。讲清默认排序规则、比较器返回值的含义、返回布尔值为什么不对,以及对象数组、中文、NaN 的排序写法。
sort 会直接修改原数组,返回值和原数组是同一个引用。讲清怎样排序而不动原数组,slice、展开、toSorted 三种写法的区别,以及复制后对象元素仍然共享的问题。
splice 会修改原数组,第二个参数是删除个数,返回被删掉的元素;slice 不改原数组,第二个参数是结束索引,返回截取的副本。附删除、插入、替换写法,负数索引规则和 toSpliced。
数组 toString 内部就是调用 join,默认用逗号连接。讲清 join 自定义分隔符、null 和 undefined 变空字符串、嵌套数组看起来被展平的原因,以及为什么 join 结果不能当序列化格式用。
push 和 pop 只动数组末尾,shift 和 unshift 要把后面所有元素的索引挪一遍,所以大数组上头部操作更慢。讲清四个方法的返回值、复杂度,以及用 shift 做队列的问题和游标写法。
reduce 不传初始值时拿第一个元素当累加器,空数组没有第一个元素,所以抛 TypeError。讲清 filter 之后突然报错的原因、求和与求最大值该怎么选初始值,以及单元素数组不执行回调的坑。
forEach 里 return 为什么停不下来?对比 for...of 和 forEach 在跳过当前项、提前结束、处理 async 回调上的区别,以及查找用 some、find,顺序等待用 for...of,并发用 Promise.all 的选择方法。
空对象读 obj.toString 也能拿到值,因为对象读属性会查原型链。讲清普通对象当字典的原型冲突问题,Map 的 get 和 has 为什么不受影响,以及 Object.hasOwn、Object.create(null) 和假值判断的坑。
Map 严格按键第一次插入的顺序遍历,更新已有键不改变位置,删除后再插入才会排到最后。对象则会把整数键按数值大小排在最前面。附对照代码、LRU 缓存的写法和对象属性排序规则。
JSON.stringify 只看对象的可枚举自有属性,Map 的数据不存在属性上,所以得到 {}。讲清转成条目数组再用 new Map 恢复的写法,Object.fromEntries 的键类型问题,以及对象键、BigInt 等特殊值的限制。
Map.set 和 Set.add 返回集合本身,所以能接着调用;delete 返回布尔值表示删没删成功,clear 返回 undefined,都不能再链下去。讲清各方法返回值、链式调用是原地修改,以及 React 里更新 Map 状态的写法。
Map.size 数的是条目,Object.keys(obj).length 数的是自有、可枚举的字符串键,数组 length 只是最大索引加一。讲清三者的统计范围、Symbol 和不可枚举属性的差别,以及为什么普通对象没有 length。
普通对象的键只能是字符串或 Symbol,数字和对象当键都会被转成字符串;Map 的键可以是任意值,对象按引用区分。附对照代码,讲清什么时候该用 Map、什么时候用对象更合适。
Set 按插入顺序遍历,遍历中新加的成员会被访问到,还没访问就被删除的会跳过,删除后重新加入的会再访问一次。讲清这三条规则、为什么可能死循环,以及先复制快照再遍历的写法。
Set 用 SameValueZero 判断重复,NaN 等于 NaN,+0 等于 -0,不做类型转换,对象按引用比较。讲清它和 === 与 Object.is 的区别,数组去重的写法,以及对象数组按 id 去重该怎么做。
数组 includes 要从头逐个比较,Set.has 通常是哈希查找,查询次数多时 Set 更快。但建 Set 本身要遍历一遍,只查一次或每次都重建就不划算。讲清复杂度、什么时候值得建 Set,以及常见的写法错误。
new Array(3) 只设置了长度,里面一个元素都没有,这叫空位。讲清空位和 undefined 的区别、为什么 map 和 forEach 会跳过空位、怎样用 fill 和 Array.from 初始化数组,以及 fill 对象时共享引用的坑。
数组展开 [...x] 靠的是迭代器协议,只要对象实现了 Symbol.iterator 就能展开,所以数组、Set、Map、字符串都行。讲清展开是浅拷贝、为什么类数组对象展开会报错,以及数组展开和对象展开的区别。
WeakMap 的键传统上只能是对象,ES2023 起也可以是非注册的 Symbol。它对键是弱引用,键对象没有其他引用时条目会被回收。讲清为什么不能用字符串当键、为什么没有 size 和遍历,以及适合的缓存和私有数据场景。
WeakSet 只能存对象和非注册 Symbol,对成员是弱引用,没有 size 也不能遍历。讲清它最适合的场景:给对象打「已处理」标记、遍历对象图时防止循环引用,以及标记失败后如何允许重试。