JavaScript 闭包是什么?
用独立计数器和循环输出题理解 JavaScript 闭包:词法作用域、变量绑定、闭包的作用与内存泄漏边界,附可运行代码、面试回答和常见追问。
JavaScript面试题第 1 页,显示第 1–50 题,共找到 285 道完整解析,可继续按分类、标签与关键词缩小范围。
按稳定语义路径排序
用独立计数器和循环输出题理解 JavaScript 闭包:词法作用域、变量绑定、闭包的作用与内存泄漏边界,附可运行代码、面试回答和常见追问。
从搜索联想和滚动采样理解防抖与节流,给出保留 this、参数并支持取消的防抖代码,以及首沿节流实现,说明 leading、trailing、请求竞态和清理边界。
以浏览器 Event Loop 为范围,逐步分析同步代码、Promise、queueMicrotask 与 setTimeout 的执行顺序,讲清微任务检查点、渲染时机与常见面试误区。
梳理 JavaScript 三种相同值规则在 NaN、正负零、对象身份上的差异,说明数值归一化、缓存键和不可变更新应如何选择比较方式。
AbortController 怎么取消 fetch 请求?用 signal 把控制器和请求连起来,调用 abort() 后 fetch 以 AbortError 拒绝。附搜索框取消旧请求、超时取消、一个 signal 取消多个请求的写法和常见坑。
async 函数里同步 throw 的错误不会直接抛给调用方,而是变成返回的 Promise 的拒绝原因。讲清为什么外层 try/catch 接不住、await 之后才接得住,以及它和普通函数返回 Promise、setTimeout 回调里抛错的区别。
async 函数不管 return 什么,调用方拿到的都是一个 Promise。讲清 return 普通值、return 另一个 Promise、return thenable 分别会怎样,为什么返回的 Promise 和 return 的不是同一个,以及怎么把值取出来。
forEach 会同步调用完所有回调,并丢掉 async 回调返回的 Promise,所以既不按顺序等,外面也不知道什么时候全做完。讲清原因,给出 for...of 串行、Promise.all 加 map 并发两种替代写法,以及错误接不住的坑。
for 循环里每次 await 都要等上一个请求回来才发下一个,是串行,总耗时是所有请求之和。用可运行的计时示例对比串行和 Promise.all 并发,讲清并发的关键是先发起再等待,以及限流、失败处理和什么时候必须串行。
async/await 底层还是 Promise:async 函数返回 Promise,await 后面的代码相当于放进 then 回调里执行。用可运行示例把同一段逻辑写成 await 版和 then 链版,讲清错误处理、finally 的对应关系,以及两种写法不完全等价的地方。
try/catch 只能接住被 await 的 Promise 拒绝和同步抛错,漏写 await、定时器回调里的错误都接不住。讲清哪些业务错误应该就地处理,哪些程序 bug 应该放给全局兜底,以及 return 和 return await 的区别。
限制并发的任务池里,先发的任务不一定先完成,直接 push 结果顺序就乱了。讲清按下标写回结果数组的做法、为什么下标要在 await 之前取,以及按完成顺序处理、按原顺序分段输出这两种变体和失败时的处理。
经典手写题:实现一个 Scheduler,最多同时运行 2 个任务,add 返回 Promise。用一个等待队列加一个运行计数器实现,附 2 3 1 4 输出的完整推演,以及同步抛错、任务失败卡死队列这些常见漏洞。
fetch 重试最常见的坑是参数丢失:Request 对象的请求体只能读一次,第二次发就报 TypeError,options 被拦截器改过也会带错。讲清保存 url 和 options 重发、每次 clone、哪些错误该重试、退避间隔和 POST 幂等问题。
AbortSignal.any 接收一组 signal,返回一个新 signal,其中任意一个被取消它就跟着取消。讲清用户取消加超时的组合写法、怎么通过 reason 区分是谁取消的、已取消的 signal 传进去会怎样,以及老环境下的手写降级。
Promise.all([]) 不会卡住也不会报错,而是返回一个已经 fulfilled、值为空数组的 Promise。讲清它为什么是同步变成 fulfilled、回调却仍然异步执行,以及 race、any、allSettled 遇到空数组时各自的表现和业务里的坑。
Promise.all 只要有一个请求失败就整体 reject,其他成功的结果全拿不到。讲两种保住结果的写法:给每个 Promise 单独 catch 兜底,或者改用 Promise.allSettled,以及关键接口和可选接口混在一起时该怎么拆。
Promise.all 数组里放数字、字符串、null 这类普通值不会报错,它们会被 Promise.resolve 包一层,结果数组里原样保留,位置也不变。讲清包装过程、thenable 对象的特殊处理、结果顺序为什么和完成顺序无关,以及传入不可迭代值时的表现。
Promise.all 是快速失败的:第一个 reject 出现时整体立刻拒绝,不等其他任务。但其他任务不会被停掉,它们照样执行完,结果和后续错误都被静默丢弃。附执行顺序演示、用 AbortController 真正停掉剩余请求的写法和常见误区。
Promise.allSettled 的结果数组里,成功项是 status 加 value,失败项是 status 加 reason,顺序和传入顺序一致。讲清两种结构、为什么要用 status 而不是 value 判断成败、TypeScript 里怎么收窄类型,以及和 Promise.all 的区别。
明明函数已经返回 Promise,还要 new Promise 再包一层,代码多了不说,内层出错时外层 Promise 可能永远 pending。这篇用一段能跑的代码演示错误是怎么丢的,讲清直接 return 原链的改法,以及什么时候才真的需要 new Promise。
Promise.any 只要有一个成功就返回,全部失败时才拒绝,拒绝值是 AggregateError,每个失败原因都在它的 errors 数组里。这篇讲清 errors 的顺序规则、空数组的特殊情况、多节点兜底请求的写法,以及 catch 里怎么区分 AggregateError 和普通错误。
Promise.race 取第一个结束的结果,不管成功还是失败;Promise.any 取第一个成功的,失败会被跳过,全部失败才拒绝。这篇用同一组任务对比两者的输出,讲清超时控制和多节点择优两个典型场景该选哪个,以及空数组时两者的不同表现。
catch 放在链中间,会把错误吃掉并让链恢复成功,后面的 then 照常执行;放在末尾,前面任何一步出错都会跳过剩下的 then 直接进来。这篇用一段对比代码讲清两种位置的输出差异、catch 的返回值怎么决定后续走向,以及 then 第二个参数和 catch 的区别。
then 回调里抛出的异常会被 Promise 自动捕获,变成链上的拒绝,后面的成功回调被跳过,直接进入 catch。这篇用代码演示错误沿链传递和恢复的过程,讲清只有同步抛出才会被捕获、setTimeout 里的 throw 为什么接不住,以及外层 try/catch 为什么无效。
new Promise 传入的 executor 是同步执行的,new 的那一刻就会被立即调用;异步的只是 then 回调。通过一段打印顺序题讲清 executor、resolve 和 then 各自在什么时候运行,以及 executor 里写异步代码、同步抛错时会怎样。
executor 里同步 throw 的错误会被 Promise 构造器捕获,Promise 变成 rejected,错误对象就是拒绝原因。讲清三个边界:resolve 之后再 throw 会被忽略,setTimeout 回调里 throw 构造器接不住,没写 catch 会触发未处理拒绝。
finally 回调拿不到结果,返回值也会被忽略,上一步的成功值或失败原因会原样传下去;只有在 finally 里抛错或返回 rejected Promise,才会改写结果。用可运行的示例讲清这三点,以及用 finally 关 loading 的正确姿势。
then 回调返回一个 Promise 时,下一个 then 拿到的是这个 Promise 最终的值,而不是 Promise 本身。讲清外层 Promise 如何等待并跟随内层的状态、thenable 为什么也会被展开、返回自身为什么报错,以及展开带来的额外微任务。
Promise.race 传入空数组,返回的 Promise 永远停在 pending,既不成功也不失败,await 它的代码会一直卡住。对比 Promise.all、allSettled、any 在空数组下的不同结果,讲清原因和业务代码里该怎么防。
Promise.resolve 收到带 then 方法的对象时,不会原样返回它,而是新建一个 Promise,在下一个微任务里调用它的 then 来决定结果。附原样返回的唯一条件、then 行为不规范时的处理和 await 的关系。
多个 Promise 同时跑,then 回调基本按谁先兑现谁先执行,而不是按创建顺序。但用 Promise 去 resolve 另一个 Promise 会多等两个微任务,顺序就会反过来。附 Promise.all 结果顺序和真实请求里的处理方式。
Promise 只认第一次 resolve 或 reject,之后的调用全部静默忽略,也不会报错。但 resolve 不会让函数停下,后面的代码照样执行。附 resolve 一个 Promise 时的锁定状态、超时封装里的应用和手写 Promise 的实现要点。
哪怕 Promise 早就兑现,then 回调也要等当前同步代码跑完才执行。这篇讲清它被放进了微任务队列、为什么规范要这样设计(避免时同步时异步的回调),以及和 setTimeout、await 的执行顺序关系。
用 Promise.race 让原任务和一个定时拒绝的 Promise 赛跑,就能实现超时。但 race 只是不再等结果,原任务照样执行完。附清理定时器的完整写法、用 AbortController 真正取消请求的方式和常见追问。
Promise 被拒绝后,如果到本轮微任务结束都没有任何处理函数,浏览器就会触发 unhandledrejection。这篇讲清几种最常见的漏处理写法、catch 挂错位置的坑、Node.js 里的默认行为,以及怎样用它做全局错误上报。
异步递归有两个坑:await 之前就递归会同步爆栈,每层都 return 下一层的 Promise 会让内存一直涨。这篇讲清两种问题的来源,为什么尾递归在 JavaScript 里靠不住,以及用 while 循环加队列改写的标准做法。
用 reduce 从 Promise.resolve(初始值) 开始,每一步 prev.then(fn),就能让函数依次执行并拿到上一步的结果。附 async/await 写法、把 fn() 写成立即调用导致变并发的经典错误、错误处理和收集全部结果的方法。
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 下标的正确写法。