map、filter、sort 链式调用会创建几个数组?怎么减少中间分配?
map 和 filter 各返回一个新数组,sort 是原地排序、返回的还是同一个数组,所以三步链式调用只新建两个数组。这篇讲清怎么数分配、合并成一个循环后回调顺序会变、reduce 里展开累加器的平方级分配,以及什么时候才值得优化。
JavaScript面试题第 2 页,显示第 51–100 题,共找到 285 道完整解析,可继续按分类、标签与关键词缩小范围。
按稳定语义路径排序
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 也不能遍历。讲清它最适合的场景:给对象打「已处理」标记、遍历对象图时防止循环引用,以及标记失败后如何允许重试。
ArrayBuffer 发给 Worker 时要么复制一份,要么转移所有权;SharedArrayBuffer 是主线程和 Worker 同时读写同一块内存,所以要用 Atomics 保证读改写不被打断。附共享计数器示例、跨源隔离要求和选择建议。
slice 复制一段字节生成新的 ArrayBuffer,原来的照常可用;transfer 把内存移交给新的 ArrayBuffer,原来的变成已分离状态、长度为 0。讲清两者的内存开销、旧视图失效的问题、transfer 改变长度的规则和兼容写法。
现代浏览器直接用 await blob.arrayBuffer(),老环境用 FileReader.readAsArrayBuffer 配合 load、error 事件。讲清两种写法、怎样把 FileReader 封装成 Promise、大文件为什么要用 stream 或分片读取,以及转文本的更简单方法。
File 继承自 Blob,多了文件名和修改时间;Blob 是不可修改的字节块;ArrayBuffer 是可读写的一段内存;TypedArray 是按固定类型解读 ArrayBuffer 的视图。附读取文件头的示例,讲清它们怎么互相转换。
解析文件头、网络包这类二进制数据时,类型化数组只能按本机字节序、按对齐位置读同一种类型,DataView 可以在任意偏移读任意宽度,并逐次指定大端或小端。附协议头解析示例和对齐报错的原因。
new Date 传数字是 UTC 毫秒时间戳,传年月日多个参数按本地时区,传字符串要看格式:只有日期按 UTC,有时间没偏移按本地,带 Z 或偏移就是确定的时间点。附对照代码、月份从 0 开始和日期溢出的坑。
夏令时切换那天,有一小时的本地时间不存在,还有一小时会出现两次。讲清 Date 遇到不存在的 02:30 为什么变成 03:30,加 24 小时和加一天为什么不一样,以及日程类业务该怎么存时间。
Date 内部只存一个 UTC 毫秒时间戳,不存时区。getHours、toString 按运行环境的本地时区显示,getUTCHours、toISOString 按 UTC 显示。讲清同一个时间点为什么在北京和纽约打印不同,以及 Date 为什么记不住输入时的偏移。
new Date 解析失败不会返回 null,也不报错,而是得到一个时间值为 NaN 的 Date 对象。讲清为什么 instanceof 判断不出来,怎么用 Number.isNaN 配合 getTime 检测,以及 2 月 31 日这种会被自动进位、根本不算无效的日期怎么校验。
setHours 改的是本地钟点,setUTCHours 改的是 UTC 钟点。讲清在东八区把时间设成当天零点为什么两者差 8 小时,跨夏令时加两小时为什么一个过了 3 小时一个过了 2 小时,以及 setter 会原地修改对象、返回时间戳这两个坑。
getTimezoneOffset 返回的是 UTC 减本地时间的分钟数,所以东八区是 -480,符号和常说的 UTC+8 正好相反。讲清它为什么和具体日期有关、半小时时区怎么格式化,以及想知道用户时区名该用什么。
toISOString 固定输出 UTC 时间、以 Z 结尾,是给机器交换数据用的;toLocaleString 按语言和时区输出给人看的文本。讲清两者的分工、用 toISOString 截日期为什么会少一天,以及显示指定时区时间的正确写法。
TextDecoder 默认会识别开头的 BOM 并把它去掉,设置 ignoreBOM 为 true 反而会把 BOM 留在结果里,名字很容易理解反。讲清 BOM 是什么、为什么 Excel 导出的 CSV 常带它、分块解码怎么处理,以及它引起的 JSON 解析失败。
数组 sort 默认按 UTF-16 编码比较字符串,中文不按拼音,item10 会排在 item2 前面。讲清 Intl.Collator 怎么按语言规则排序,numeric 和 sensitivity 两个常用选项,以及它和 localeCompare 的关系。
给 Intl.DateTimeFormat 传 timeZone 选项,就能把同一个 Date 显示成任意地区的当地时间,不用改服务器时区,也不用自己加减小时。讲清 locale 和 timeZone 的分工、IANA 时区名的写法、formatToParts 拼自定义格式,以及 SSR 时的时区一致性。
把几个名字连成一句话,中文用顿号加「和」,美式英文最后一项前还要加逗号,德语又不加。讲清 Intl.ListFormat 的 format 用法,type 的 conjunction、disjunction、unit 三种语义,以及用 formatToParts 给每一项加链接的写法。
金额格式不只是前面加个符号:千分位、小数位、符号放前还是放后、负数怎么写,各国都不一样,日元还没有小数。讲清 Intl.NumberFormat 的 style currency 用法、currencyDisplay 怎么区分美元和加元,以及格式化不能解决浮点精度的问题。
Intl.NumberFormat 的 notation 选项有 standard、scientific、engineering、compact 四种。讲清科学计数法和工程计数法的区别、有效数字怎么控制、和 toExponential 有什么不同,以及 988M、9.9亿这种紧凑写法该用哪个选项。
英语只有单数和复数两种,俄语有三种,阿拉伯语有六种,用 count === 1 判断只对英语有效。讲清 Intl.PluralRules 的 select 返回什么、怎么配合文案表使用、ordinal 模式怎么生成 1st 2nd 3rd,以及为什么文案表必须有 other。
Intl.RelativeTimeFormat 的 format 接收一个带正负号的数值和单位,负数表示过去,正数表示未来,numeric 设为 auto 还能输出昨天、明天。讲清它只负责措辞、不负责算时间差,以及怎么自己写一个自动选单位的 timeAgo 函数。
split 按空格切不了中文,length 数 emoji 会多算,Intl.Segmenter 按语言规则把文本切成用户看到的字符、单词或句子。讲清 grapheme、word、sentence 三种粒度,isWordLike 和 index 的含义,以及字数统计、安全截断这些常见用法。
Intl 的构造器遇到不支持的语言不会报错,而是悄悄回退到更通用的语言或运行环境默认值。讲清 supportedLocalesOf 怎么检测支持情况、resolvedOptions 怎么看最终用了哪种语言,以及为什么应用还要有自己的语言回退,不能只依赖 Intl。
JSON.stringify 遇到 BigInt 会抛 TypeError,转成 Number 又会在超过 2 的 53 次方后悄悄丢精度。讲清为什么要用 replacer 转成字符串、接收端怎么按字段转回 BigInt、为什么不建议改 BigInt.prototype.toJSON,以及后端返回大数字 ID 怎么办。
对象里有属性指回自己或祖先时,JSON.stringify 会抛 TypeError: Converting circular structure to JSON。讲清 WeakSet 版 replacer 为什么会误伤共享引用,怎么用祖先栈区分真正的环,以及深拷贝为什么该用 structuredClone。