JavaScript面试题库
收录真实面试常见问题,答案长度适合口头表达;拿不准时可返回相关主题复习。
语言核心
48个问题 · 0 已看01 ===、Object.is与SameValueZero有何区别,选择哪一种会在什么场景产生影响? 查看答案 ▾ 收起 ▴
02 JavaScript如何通过原型链查找属性,又该怎样安全地判断属性归属? 查看答案 ▾ 收起 ▴
读取属性时,运行时先检查对象自身,再沿内部原型链接逐层查找,直到找到该键或到达null。自有属性可以遮蔽继承属性,包括访问器。in运算符会同时报告自有和继承属性,因此不能用来判断归属。应使用Object.hasOwn(object, key),尤其当对象可能具有null原型或覆盖了hasOwnProperty时。在输入边界还要注意,赋值可能触发继承的setter。若接口接受任意键,应使用明确允许列表,或使用null原型字典。
03 为什么JavaScript私有字段不同于以下划线开头的普通属性? 查看答案 ▾ 收起 ▴
04 与修改型数组方法相比,复制型数组方法提供哪些保证? 查看答案 ▾ 收起 ▴
sort、reverse、splice等修改型方法会改变接收者;对应的toSorted、toReversed、toSpliced会返回不同的外层数组,并保持接收者不变。但这个保证只是浅层的:元素对象仍被共享,因此修改嵌套对象会从两个数组中同时可见。不同修改型方法的返回值也不一致,不能由修改行为推断。例如sort返回接收者,splice返回被删除的元素。在状态代码中,判断更新是否不可变时必须同时追踪外层数组标识和嵌套对象标识。
05 ES模块导入是“实时绑定”意味着什么,为什么循环依赖仍可能失败? 查看答案 ▾ 收起 ▴
ES模块导入是导出方绑定的只读视图,而不是复制出的快照。导出模块之后更新let绑定时,导入方会看到新值,但不能通过导入名称赋值。执行前,加载器会先链接模块图,因此许多循环依赖可以建立。若顶层执行在某个模块初始化词法导出之前就读取它,循环仍会因暂时性死区而报错,或产生依赖顺序的行为。应打破循环,把读取延迟到函数调用时,或把共享契约移到更低层模块。
06 对象满足哪些契约才可迭代,循环提前结束时如何执行清理? 查看答案 ▾ 收起 ▴
可迭代对象提供Symbol.iterator,并由它返回迭代器。每次调用next都返回含有done、以及必要时含有value的对象。可迭代对象与迭代器可以是同一对象,也可以不是。for…of因break、return或异常完成而提前退出时,会执行迭代器关闭:若存在return方法就调用它。生成器可借此在finally中清理资源。但这不表示手动消费的所有迭代器都会自行清理;直接调用next的代码仍必须定义并遵守所有权与关闭策略。
39 为什么forEach不会等待async回调,哪些写法能明确表达完成语义? 查看答案 ▾ 收起 ▴
40 数组方法如何处理空位,为什么展开稀疏数组会改变后续行为? 查看答案 ▾ 收起 ▴
在Node 24中,map()、filter()和forEach()等许多迭代方法会跳过没有对应属性的索引,而for...of与数组迭代器会在这些位置产生undefined。map()会在结果中保留空位;展开数组则会把它们具体化为undefined元素,之后的回调就会访问它们。这会影响计数、校验和序列化。不要用空位表示业务状态;应显式归一化,或在必须区分空位与存储的undefined时使用index in array。
51 const真正使什么不可变,又该如何单独审查对象所有权? 查看答案 ▾ 收起 ▴
在Node 24中,const只禁止一个词法绑定被重新赋值,不会冻结该绑定引用的对象。属性与嵌套对象仍可变,其他别名也会观察到同一次修改。Object.freeze()只限制目标对象自身这一层,嵌套引用和访问器副作用仍可能改变状态。审查时应分开判断绑定稳定性与数据所有权。若调用方需要快照,应只在标识必须分离的层级构造新对象,并测试嵌套标识。盲目深度冻结可能错误处理环、类实例、Proxy和资源,同样需要明确契约。
52 为什么真值检查不等于存在性或有效性检查? 查看答案 ▾ 收起 ▴
在Node 24中,条件会把值转成Boolean,因此0、-0、0n、NaN、false、空字符串、null和undefined都是假值。空数组、空对象以及"false"这样的字符串却是真值。所以if (value)无法说明字段是否存在、数组是否有元素,或文本是否表示布尔值。只有null与undefined表示缺失时使用??;关心属性是否存在时使用Object.hasOwn();类型、语法与范围则要按业务契约校验。用||提供默认值会静默覆盖有效的零、false或空字符串。
53 let、const与var在声明语句执行前有何不同? 查看答案 ▾ 收起 ▴
在Node 24中,进入作用域时,let和const已经创建了块级绑定,但在声明执行前,它们一直位于暂时性死区且尚未初始化。提前读取任意一个都会抛出ReferenceError;const还必须在声明时初始化,之后不能重新赋值。var属于函数或全局作用域,并在作用域建立时初始化为undefined,因此提前读取会成功,却可能遮蔽顺序错误。“提升”不表示源码被移动。应优先使用词法声明并明确安排初始化顺序;如果已声明的导出仍未初始化,还要检查模块循环。
54 哪些日期字符串适合安全接收,又该如何检测无效Date? 查看答案 ▾ 收起 ▴
在Node 24中,规范定义的日期时间格式是可移植基线:Z表示UTC,显式偏移量标识一个时刻,仅日期形式按UTC解释,不带偏移量的日期时间则按本地时间解释。其他人类习惯格式可能由实现决定,看似合理的组件也可能被归一化而非报错。边界必须定义唯一语法与时区策略,再用Number.isNaN(date.getTime())检查;无效Date对象仍是真值。校验前不要调用会抛出RangeError的toISOString(),也不要仅靠正则证明日期真实存在。
55 为什么增加86,400,000毫秒不总等于增加一个日历日? 查看答案 ▾ 收起 ▴
在Node 24中,增加86_400_000会让一个时刻精确前进24个经过小时。本地日历日在夏令时转换处可能只有23或25小时,因此“明天同一本地时间”是另一个需求。Date只保存时刻,不保存命名时区或重复规则。过期时长应使用经过时间算术;日历重复则应保留墙上时间字段和IANA时区,并定义空缺与重叠的处理规则。本地setter使用主机时区且会修改接收者,所以应先复制,并测试相关转换的两侧。
56 Date值穿过JSON边界时会发生什么,无效Date又会怎样? 查看答案 ▾ 收起 ▴
57 JSON.parse成功只能证明什么,值被信任前还必须做什么? 查看答案 ▾ 收起 ▴
在Node 24中,JSON.parse()成功只证明完整输入符合JSON语法;它仍可能返回null、原始值、数组,或缺少必填字段的对象。解析不能证明schema有效、业务不变量成立,也不代表已授权。捕获语法错误后,还要校验顶层类别、属性归属、每个嵌套类型、范围与允许值,再从允许列表构造领域对象。不要捕获所有失败并返回{},否则非法输入会伪装成合法空数据。语法、校验与权限失败应保持区分,让调用方和日志保留真正失败的边界。
58 JSON契约应如何传输超出Number精确表示范围的整数? 查看答案 ▾ 收起 ▴
在Node 24中,JSON数字语法可以包含超出JavaScript Number精确范围的整数,而解析结果已经丢失精度。对这个已舍入的值执行BigInt(value)不能恢复原始数字,JSON.stringify()遇到原始BigInt还会抛错。可移植契约应使用经验证的十进制字符串,明确字段含义,并在语法和范围检查后再转换为BigInt。Node 24还为原始值提供reviver源文本上下文,schema已知字段可读取context.source,但这属于运行时特定边界。应测试最大值与跨语言生产者,不要用通用stringify结果冒充等价性。
59 解构模式中不同层级的默认值分别如何生效? 查看答案 ▾ 收起 ▴
在Node 24中,只有当前模式位置取出的值精确为undefined时,解构默认值才会执行。在function read({ profile: { name = 'guest' } = {} } = {})中,参数默认值保护被省略的外层参数,profile默认值保护值为undefined的属性,叶子默认值保护值为undefined的name。它们都不会替换显式null。对不可信形状,应在复杂模式前校验,或在契约允许时用??归一化外层空值。常见错误是认为叶子默认值能保护缺失的中间对象;实际上求值在到达叶子前就会失败。
60 为什么对象rest不能作为安全脱敏边界,它究竟复制了什么? 查看答案 ▾ 收起 ▴
在Node 24中,对象rest会创建新的普通对象,并复制剩余的自有、可枚举字符串与Symbol属性。它排除继承属性和非可枚举属性,读取时会调用getter,把访问器转成数据值,并共享嵌套对象引用。{ password, ...publicUser }这样的代码是拒绝列表:新增token或密钥会自动泄露。日志与响应应从公开字段允许列表构造,再逐个决定嵌套值需要复制到哪一层。只有未知字段应被保留时,对象rest才合适;它不适合保护未知字段。
61 数组解构如何消费并关闭迭代器? 查看答案 ▾ 收起 ▴
62 为什么能编译现代JavaScript语法,不能证明部署后的模块一定能运行? 查看答案 ▾ 收起 ▴
以Node 24为基线时,兼容性包含相互独立的解析器、转换、运行时与主机层。编译器可以改写箭头函数或可选链等语法,却可能保留目标运行时缺少的内建API,也不能让浏览器DOM API在Node中出现。模块指定符解析也由主机决定;bundler别名或省略扩展名的import在原生执行时可能失败。应先定义部署运行时与产物,再在Node 24中运行该产物。只为必需API添加polyfill,并验证转换保留迭代器关闭等语义;源码构建成功不等于端到端兼容。
63 生成器的yield、next、return与throw之间会传递哪些值? 查看答案 ▾ 收起 ▴
在Node 24中,调用生成器函数只会创建暂停的迭代器,不会立即执行函数体。第一次next(value)会启动执行,但由于尚无等待接收的yield,其参数会被忽略。之后的next(value)会让上一个暂停的yield表达式求值为该值。iterator.return(value)请求完成并允许finally执行,iterator.throw(error)则在暂停点注入异常。生成器最终返回值的done为true,不会被for...of或展开收集。双向生成器很紧凑,但容易误用;若消息顺序属于业务协议,应优先使用命名方法。
64 应用应如何用动态import安全地选择模块? 查看答案 ▾ 收起 ▴
在Node 24中,import()会按主机解析规则处理指定符,并返回解析为模块namespace的Promise。让外部输入拼接指定符会扩大可解析范围,还可能让bundler无法发现目标。应把公开名称映射到固定loader函数,拒绝未知名称,并要求所有目标暴露同一份已记录的形状。在await import()边界捕获解析与求值拒绝,并与导出业务调用的失败区分。不要用时间戳query做缓存破坏;不同已解析URL会创建不同模块标识,重复顶层副作用,并可能使模块状态持续增长。
65 为什么try...catch可能捕获不到async函数调用的拒绝? 查看答案 ▾ 收起 ▴
在Node 24中,调用async函数会立即返回Promise。如果在try中发起调用却没有await,后续拒绝属于该Promise,发生时同步控制流已经离开该代码块,因此外层catch不会收到它。应在处理策略边界内等待操作,把Promise返回给有所有权的调用方,或为有意后台任务附加终结拒绝处理器。async函数中即使在第一个await前抛错,也会拒绝其Promise。核心陷阱是发起工作时没有为拒绝、取消、关停与可观测性分配所有权。
66 finally如何可能意外覆盖返回值或错误? 查看答案 ▾ 收起 ▴
在Node 24中,无论try或catch是返回还是抛错,finally都会在控制流离开前执行。如果finally自己执行了return或throw,这个新完成会替换正在传播的完成,有效结果或原始失败可能因此消失。应让finally只负责幂等清理,并正常结束。若清理也可能失败,必须明确规定是主错误、清理错误还是聚合错误跨越边界,不能依赖偶然优先级。还要记住finally不是进程关停保证,持久恢复不能只依赖JavaScript清理一定执行。
67 如何区分Map中缺失的键与值为undefined的已存在键? 查看答案 ▾ 收起 ▴
在Node 24中,键不存在和键显式存储undefined时,map.get(key)都会返回undefined。当“是否存在”本身有意义时,要先用map.has(key),再读值。get(key) || fallback的覆盖范围更广:它还会替换已存储的0、false和空字符串。??能保留这些假值,但仍无法区分缺失与已存储的undefined。应先定义值域,再选检查方式。对不允许undefined的计数器,(counts.get(key) ?? 0) + 1很清楚;对三态数据,则应显式按has()分支。
68 为什么Set不会去重字段相等的对象,应该改用什么? 查看答案 ▾ 收起 ▴
在Node 24中,Set成员和Map键都使用SameValueZero。原始值的边界行为包括只保留一个NaN和一个零,但对象按标识比较,因此两个独立的{ id: 7 }仍是两个成员。如果业务标识是id,应以该稳定值作为Map键,并决定保留第一条还是最后一条记录。除非规范化规则完整处理属性顺序、不支持的值和冲突,序列化对象不是安全的通用键。显式归一化需要额外工作,但它使去重语义可测试,而不是偶然依赖引用复用。
69 floor、ceil、trunc与round对负数的处理有何不同? 查看答案 ▾ 收起 ▴
在Node 24中,Math.floor()向负无穷方向取整,ceil()向正无穷方向取整,trunc()向零方向截断。Math.round()选择最近整数,精确中点向正无穷方向处理,因此Math.round(-2.5)是-2;Math.round(-0.5)还可以产生负零。这些规则不是金融舍入或银行家舍入。选择API前要先命名所需方向或中点策略,并测试正负小数与-0。不要用value | 0或~~value,它们会转换为32位有符号整数,对大值或非有限值可能回绕。
70 为什么Number.EPSILON不是浮点数比较的通用容差? 查看答案 ▾ 收起 ▴
在Node 24中,Number.EPSILON是1与下一个更大可表示Number之间的距离。它不描述任意量级上的测量噪声或舍入误差。固定epsilon对大值可能过于严格,在零附近也可能不合适。应从领域误差预算推导绝对和相对容差,常见判定是|a-b|小于绝对下限,或小于较大量级的某个相对比例。离散计数和标识符仍应使用精确相等。常见陷阱是把所有比较都改成近似比较,从而模糊真实业务阈值。
71 哪些情况下Math.random是错误的随机源? 查看答案 ▾ 收起 ▴
在Node 24中,Math.random()返回[0, 1)内近似均匀的Number,没有标准种子接口,也不提供密码学强度保证。它适合视觉变化和普通非安全抽样,不适合重置token、验证码、会话标识符或可重现模拟。安全用途应使用Web Crypto或经审查的高层API;把安全随机字节映射到任意区间时,仍要避免取模偏差。测试与模拟应注入可设种子的伪随机源并记录种子。映射值之前还要定义空区间和端点是否包含,否则索引公式可能有偏或产生非法值。
72 WeakMap何时有助于内存所有权,为什么它不能替代清理? 查看答案 ▾ 收起 ▴
在Node 24中,WeakMap不会仅因键存在于集合中,就让对象或非注册Symbol键保持可达。它适合生命期应跟随外部已拥有对象的元数据。它不会弱化listener、timer、数组、闭包或其他缓存中的引用,也永远不会关闭socket或取消handler订阅。资源必须使用显式、最好幂等的close()、unsubscribe函数或abort signal。其取舍之一是可观测性:WeakMap没有迭代、size或确定性淘汰信号,需要指标或枚举时应使用带有界策略的Map。
73 为什么WeakMap和WeakSet不提供size与迭代? 查看答案 ▾ 收起 ▴
在Node 24中,弱键变得不可达后,对应条目可能消失,但垃圾回收时机是有意不作规定的。如果暴露size、键列表或迭代,应用行为就能观察内存压力与回收调度,枚举本身还可能使键重新可达。因此弱集合只支持通过调用方已持有的键进行按标识查询和删除。它们不适合dashboard、序列化、容量上限或确定性过期。这些场景应使用带显式淘汰策略的普通Map或Set。绝不要为了枚举WeakMap而维护并行强键列表,因为该列表会抵消弱生命期的优势。
74 Node 24中哪些值可以作为弱键,它们又如何匹配? 查看答案 ▾ 收起 ▴
在Node 24中,WeakMap的键与WeakSet的成员必须是可回收对象或非注册Symbol。字符串、数字以及Symbol.for()返回的注册值不符合要求,会导致TypeError;把字符串装箱只会创建具有错误查询标识的临时对象。匹配使用对象或Symbol标识,而不是字段或描述,因此重建{ id }不会命中先前对象下的条目。若调用方通过稳定字符串或数字标识数据,应使用Map。只有调用方已经携带规范对象,且元数据应与它共享生命期时,才选择弱集合。
75 Object.defineProperty的默认标志与普通赋值有何不同? 查看答案 ▾ 收起 ▴
在Node 24中,对象字面量属性或普通赋值通常会创建可写、可枚举、可配置的数据属性。Object.defineProperty(target, key, { value })会把所有省略的布尔描述符标志默认为false,从而产生不可写、不可枚举、不可配置的属性。之后的写入可能在严格模式下抛错,枚举或删除也会像是忽略了该属性。应写明契约依赖的每个标志,并用Object.getOwnPropertyDescriptor()检查。把属性设为不可配置基本是单向决定,必须先确认后续初始化、测试或迁移不需要重新定义。
76 Object.assign复制对象时会丢失什么,又会执行什么? 查看答案 ▾ 收起 ▴
在Node 24中,Object.assign()会从左到右读取每个源对象的可枚举自有字符串与Symbol键,再把当前值写入目标。读取会执行源getter,写入可能执行目标setter,后面的源会覆盖前面的键,而目标本身会被修改。该操作不保留源描述符或原型,嵌套对象也只复制引用。必须保留描述符行为和原型时,可使用Object.getOwnPropertyDescriptors()配合Object.create()。对不可信输入,不要把任意键assign到高权限普通对象;应构造允许列表记录或有意设计的null原型字典。
77 为什么空handler的Proxy也可能破坏访问私有字段的方法? 查看答案 ▾ 收起 ▴
在Node 24中,proxy是不同于target的接收者,不会继承target的私有标记或内建内部槽。调用proxy.method()时可以找到target的方法,但调用会把proxy作为this,于是this.#field抛出TypeError。Map方法等需要内部标记的内建API也有同类问题。因此空handler并不对所有对象透明。应优先编写显式adapter。把选定方法绑定到target可能有效,但会改变函数标识、链式返回行为,以及target是否泄露,因此必须测试每个公开方法、getter、setter与回调路径。
78 包含私有字段的类应如何定义复制与序列化? 查看答案 ▾ 收起 ▴
在Node 24中,私有元素不是普通属性,因此对象展开、Object.assign()、反射、structuredClone()与默认JSON序列化都不会复制类的私有标记和状态。冻结实例也不会阻止类方法修改私有字段。应定义只包含预期字段的公开数据投影或toJSON(),在边界校验,并提供能恢复不变量的构造器或静态工厂。若getter直接返回私有可变数组,仍会泄露同一引用;应返回契约所需的投影或复制深度,不能把#名称当成完整封装。
79 哪些Proxy不变量违反常在target被冻结或禁止扩展后才出现? 查看答案 ▾ 收起 ▴
在Node 24中,Proxy trap不能报告与target受保护事实矛盾的对象形状。ownKeys不能返回重复键,不能省略不可配置的自有键,也不能为不可扩展target添加键。get trap不能为不可写且不可配置的数据属性伪造不同值,写入相关trap也有对应限制。即使宽松测试在可扩展target上通过,违反规则仍会抛出TypeError。应从匹配的Reflect结果开始,只做最小转换。测试必须覆盖不可配置属性、只读访问器与Object.preventExtensions();把这类异常当成调用方错误,会遮蔽已损坏的handler契约。
80 为什么单个Proxy trap很少足以执行完整的对象策略? 查看答案 ▾ 收起 ▴
在Node 24中,基本对象操作有各自独立的trap。set中的校验不会拦截Object.defineProperty(),而在get中隐藏某个键,也不会自动将它从in、Reflect.ownKeys()、描述符查询或删除中隐藏。因此只由一个trap实现的策略可能被绕过,或给出互相矛盾的观察。应根据真实调用方能力建立操作矩阵,决定哪些转发、哪些拒绝,并使用匹配的Reflect方法和原始receiver。如果调用方仍持有target,或能执行未覆盖操作,Proxy也不是可靠安全边界;授权应优先使用窄而明确的API。
81 Symbol()、Symbol.for() 与知名Symbol在标识和所有权上有何不同? 查看答案 ▾ 收起 ▴
在Node 24中,每次调用Symbol(description)都会创建新的非注册标识;描述相同并不会让Symbol相等。Symbol.for(key)会复用运行时级注册表中的标识,Symbol.keyFor()也只能报告已注册键。Symbol.iterator等知名值则是语言定义的固定协议键。若扩展键由某个package拥有,应导出一个普通Symbol;只有需要有意的、带namespace的运行时约定时,才使用注册表。注册标识不会自动穿过JSON、进程或持久化,且任何知道键的代码都能取回它,所以它既不是保密存储,也不是持久标识符。
82 哪些对象操作会包含Symbol键,它们为什么并不私有? 查看答案 ▾ 收起 ▴
在Node 24中,Object.keys()和for...in排除Symbol键,而Object.getOwnPropertySymbols()与Reflect.ownKeys()会显示它们。对象展开和Object.assign()会复制可枚举的自有Symbol键,默认JSON序列化却忽略它们。可枚举性与键类型是两个独立维度,所以在某项操作中不显眼不等于访问控制。持有该Symbol的调用方可直接读取属性,反射也能发现未知Symbol。Symbol适合避免扩展键冲突;语言级封装应使用私有字段或闭包状态。跨边界时要定义准确的键选择矩阵,不能假设复制与校验会看到同一组属性。
83 定义Symbol.iterator时,向JavaScript消费者承诺了什么? 查看答案 ▾ 收起 ▴
84 字符串length与正则匹配何时无法代表用户看到的字符? 查看答案 ▾ 收起 ▴
85 如何通过replace或replaceAll按字面量插入不可信的替换文本? 查看答案 ▾ 收起 ▴
86 为什么对同一个全局或黏连RegExp反复调用test会产生交替结果? 查看答案 ▾ 收起 ▴
在Node 24中,对带g或y的RegExp调用test()和exec()时,会从可变的lastIndex开始。匹配成功会把它推进到匹配结尾,失败则重置为零。因此复用一个模块级matcher,会让布尔测试依赖先前调用或其他异步操作。只关心是否存在时应移除g;独立操作应创建新表达式;或让一个scanner显式拥有状态并初始化lastIndex。黏连y还要求匹配精确从该索引开始。测试应对同一输入重复调用,并交错处理两个输入,以暴露意外共享状态。
87 比较用户可见文本时,什么情况下把两边都转成小写是错的? 查看答案 ▾ 收起 ▴
在Node 24中,toLowerCase()会执行Unicode大小写转换,但不能表达某个locale的完整搜索或排序规则、规范等价性,也不是标识符安全策略。对用户可见文本,应用目标locale和sensitivity配置一个Intl.Collator,并只把compare()结果解释为负、零或正;具体数值大小没有保证。协议标识符可能要求ASCII大小写折叠或精确代码单元相等。不要把展示locale的排序规则用于授权键。应测试目标语言与规范化形式,因为良好的UI比较可能有意把安全边界必须区分的字符串视为相等。
88 哪些类型化数组操作会共享字节,哪些会创建独立存储? 查看答案 ▾ 收起 ▴
在Node 24中,view.subarray()会在同一backing buffer上创建另一个窗口,view.slice()则分配空间并复制选定元素。以另一个view构造类型化数组会转换并复制元素值;以view.buffer、offset和length构造则会重新解释共享字节。这些形式看起来可替换,却定义了不同所有权。如果被调方会在调用后保留数据,应按生命周期复制或转移;借用的共享view会让调用方后续写入改变结果。应双向测试修改传播,并保留byteOffset和byteLength,尤其是处理池化Node Buffer窗口时。
89 为什么二进制解析器应使用显式字节序和精确窗口的DataView? 查看答案 ▾ 收起 ▴
在Node 24中,多字节类型化数组使用运行时的本机字节序,而DataView通过显式littleEndian参数读写每个字段;省略或传false表示大端。接收Uint8Array或Buffer的解析器还必须用new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)保留其局部窗口。只使用bytes.buffer可能暴露池中无关字节。每次读取字段前都要检查剩余长度,然后校验有无符号与业务范围。来自协议的固定字节fixture比encoder-decoder往返测试更能发现端序错误,因为后者两端可能共享同一个错误假设。
90 ArrayBuffer被resize或transfer后,类型化数组view会发生什么? 查看答案 ▾ 收起 ▴
在Node 24中,使用maxByteLength创建的ArrayBuffer可以resize。length-tracking view会跟随可用存储;固定长度view在缩小导致其声明范围无法完整容纳时,会整体越界。它可能报告长度为零,再次扩容后恢复为界内,但已丢弃的字节不会恢复。transfer()会创建目标存储并分离源buffer,使所有旧view失效。resize或transfer应有一个明确owner,旧引用应视为已消费。变量仍在作用域中不代表字节仍可访问,所以要测试异步交接、缩小边界以及失效后使用的每个方法。
函数与作用域
17个问题 · 0 已看07 普通JavaScript函数的this是如何确定的? 查看答案 ▾ 收起 ▴
对普通函数而言,this主要由调用形式决定,而不是由函数定义位置决定。new会创建接收者,并具有最强的相关绑定;call、apply或绑定函数提供显式接收者;object.method() 使用点号左侧的对象;严格模式下的普通调用得到undefined。因此const f = object.method会丢失隐式接收者。应在边界处通过对象调用、使用包装函数,或只绑定一次。bind会创建新的函数标识;若API之后要移除同一回调,就必须保存该标识。
08 哪些情况下箭头函数不能替代普通函数? 查看答案 ▾ 收起 ▴
当API需要动态接收者、需要通过new构造、需要函数自身的arguments对象或生成器行为时,箭头函数并不适合。箭头函数从外层词法捕获this、arguments、super和new.target;call、apply、bind都不能替换捕获的this。因此,它适合需要保留外层方法接收者的回调,却不适合作为必须操作点号左侧对象的原型方法。还要区分表达式体与块体:隐式返回对象字面量时必须加括号,否则花括号会被解析为块。
09 闭包保留的是什么,它为什么既能保存状态,也可能让内存继续被占用? 查看答案 ▾ 收起 ▴
10 call、apply、bind有何区别,包括绑定函数被用于构造时的行为? 查看答案 ▾ 收起 ▴
call立即调用函数,参数逐个列出;apply也立即调用,但从类数组值提供参数;bind不会立即调用,而是返回一个固定接收者并可预置前置参数的新函数。当输入可迭代时,展开语法通常比apply更清楚。绑定函数若与new一起使用,仍会构造原目标:新实例成为this,因此绑定的thisArg被忽略,但预置参数仍然有效。对箭头函数bind也不能改变其词法this。不要在每次注册时重新bind,因为每次都会产生不同回调标识。
11 在实际代码中,柯里化、偏函数应用、函数组合与pipe有何区别? 查看答案 ▾ 收起 ▴
柯里化把概念上接收多个参数的函数转换为分阶段接收参数的调用链,通常每阶段接收一个参数组。偏函数应用只是固定部分参数并返回接收其余参数的函数,不要求形成一元链。函数组合把前一函数输出连接到后一函数输入;compose通常从右向左读,pipe从左向右读。可复用辅助函数必须明确元数、占位符、接收者、同步或异步以及错误行为。只依赖Function.length很脆弱,因为默认参数和剩余参数不能完整表达语义输入契约。
12 防抖与节流有何区别,生产级包装器应暴露哪些生命周期控制? 查看答案 ▾ 收起 ▴
防抖会等待一段安静时间并把一串调用合并,适合用户停止输入后再搜索。节流把执行频率限制在设定上限内,适合连续滚动或指针更新。必须明确leading与trailing行为,因为它决定哪次调用提供参数,以及最后一次更新是否执行。生产级包装器应保留预期接收者,有意保存最新参数,并暴露cancel;flush也常有价值。组件卸载时要取消计时器,并中止已被替代的异步请求,因为不再调用回调并不会取消已经启动的工作。
41 把函数声明改成const箭头函数时,哪些语义会改变? 查看答案 ▾ 收起 ▴
42 类字段中的箭头函数与原型方法有何不同? 查看答案 ▾ 收起 ▴
43 严格模式与非严格模式函数如何处理传给call或apply的thisArg? 查看答案 ▾ 收起 ▴
在Node 24中,call()和apply()会把指定的thisArg原样传给严格模式的普通函数。非严格模式函数会用全局this值替换null或undefined,并对数字等原始值接收者进行装箱。ES module和类体自动使用严格模式,但最终由被调函数自身的严格性决定接收者是否归一化。箭头函数使用词法this,会忽略指定接收者。不应把非严格装箱或全局替换当成API特性;应使用严格代码并校验显式接收者。
44 apply何时能接受展开语法会拒绝的参数,两种写法又共享什么限制? 查看答案 ▾ 收起 ▴
在Node 24中,fn.apply(receiver, args)接受具有索引属性和length的类数组对象,它不必实现迭代协议。fn(...args)则要求可迭代对象,因此普通的{0: value, length: 1}可以传给apply,却不能直接展开。对数组和其他可迭代对象,展开通常更清楚;apply在类数组边界仍有用。两者都会把集合转成独立调用参数,因而受引擎参数数量上限约束。不要把无界集合展开到Math.max()等调用中;应逐项聚合或分块处理。
45 super.method() 如何选择实现同时保留this,脱离实例调用又会破坏什么? 查看答案 ▾ 收起 ▴
46 默认参数究竟何时执行,又应如何处理null? 查看答案 ▾ 收起 ▴
在Node 24中,只有参数被省略或精确为undefined时,默认初始化器才会在调用期间执行。显式传入的null、0、false或空字符串都会保留。初始化器在每次调用中从左到右执行,所以后面的默认值可以引用前面的参数,其中创建的对象也会在每次适用时重新创建。必须在边界决定null是缺失还是非法:前者可用??归一化,后者应拒绝。解构参数还需外层默认值,才能安全处理缺失的undefined。
47 为什么let循环中创建的闭包会看到不同值,而var闭包通常不会? 查看答案 ▾ 收起 ▴
在Node 24中,带词法let声明的for循环会为每次迭代创建新绑定,因此每个闭包都会解析到对应迭代的变量。var是函数作用域声明,只提供一个共享绑定;稍后执行的回调通常都会看到最终值。闭包保留的是绑定,而不是冻结的值副本,所以在循环外重新赋值被捕获的let也可能出现同类问题。循环索引应优先用let,或把所需值传入小型工厂。还要判断捕获对象是共享还是每次迭代独有;let不会克隆外部可变对象。
48 如何证明某个闭包在无意中持续占用内存? 查看答案 ▾ 收起 ▴
49 为什么Function.length不适合作为通用curry辅助函数的完成规则? 查看答案 ▾ 收起 ▴
在Node 24中,Function.length只计算第一个带默认值的参数之前的参数,不计剩余参数,一个解构模式也只算一个参数。它是描述性元数据,不是业务元数。因此curry辅助函数可能只收到base就执行(base, options = {}, request),而仅有剩余参数的目标会报告零。对可选或可变签名应显式传入元数,或改用命名清晰的配置工厂。测试要覆盖支持的参数分组、额外与空调用、占位符,以及业务函数真正执行的时点。
50 异步pipe必须定义哪些同步组合辅助函数没有的契约? 查看答案 ▾ 收起 ▴
在Node 24中,同步pipe会把每个返回值直接传给下一阶段;如果某一阶段返回Promise,下一阶段会把它当成普通对象。异步pipe应采用统一的等待规则,常见做法是从Promise.resolve(input)开始链式执行,使普通值与Promise都被等待。它还必须定义拒绝如何传播、谁拥有取消权,以及阶段是否只能串行。输入输出形状变化的阶段应命名。辅助函数内的全捕获fallback很危险,它可能把拒绝转成远离责任边界的假成功。
91 为什么在Node.js中不应从顶层this获取应用上下文? 查看答案 ▾ 收起 ▴
在Node 24中,顶层this取决于模块格式:在ES module中它是undefined,CommonJS文件则运行在包装函数内,顶层this指向module.exports,而不是globalThis。在这里创建的箭头函数会捕获该环境,无法通过call、apply或bind重新绑定。移动文件或修改最近package的type可能因此破坏原有假设。应显式传递依赖,或在拥有接收者的方法中创建回调。不要把顶层this当成可移植全局存储;应使用明确模块导出,只在确实需要进程级所有权时才使用globalThis。
异步
8个问题 · 0 已看13 任务与微任务如何交互,微任务又怎样阻塞浏览器工作? 查看答案 ▾ 收起 ▴
一个任务中的JavaScript会运行到完成。随后进入微任务检查点,宿主会清空已排队的Promise reaction、queueMicrotask回调及其他微任务,包括它们新加入的微任务,然后才处理下一个任务,并通常在此后渲染。这解释了为什么同一轮中已兑现Promise的回调会先于零延迟定时器执行。边界也很重要:不断自我追加的微任务链会推迟定时器、输入处理和绘制。大量工作应拆成有上限的块,并通过任务级调度主动让出机会。
14 怎样判断多个异步操作应串行执行还是并发执行? 查看答案 ▾ 收起 ▴
首先建模数据依赖和副作用依赖。若操作B需要A的结果,就应等待A后再启动B;若操作彼此独立且允许重叠,应先创建它们的Promise,再统一等待,常见方式是Promise.all。连续写await A(); await B(); 会强制串行,而先创建两个Promise才能重叠执行。但并发不等于无限并行:大量输入通常需要worker池、速率限制或背压。还应保留结果对应的输入标识,定义失败策略,并传递取消信号以协作停止废弃工作。
15 主要Promise组合器分别表达什么失败策略,又有哪些事情它们不会做? 查看答案 ▾ 收起 ▴
Promise.all要求所有输入兑现,并在某项拒绝时拒绝。allSettled等待每项结束,返回带状态标签的记录。any在首项兑现时兑现,只有全部输入都拒绝时才以AggregateError拒绝。race则采用首个结束项,无论兑现还是拒绝。只要组合器返回结果数组,其顺序按输入顺序,而不是完成顺序。关键是,组合结果提前结束不会取消其他操作。应先依据业务成功条件选择组合器,再加入AbortSignal等协作式取消,并等待底层操作所需的清理。
16 为什么异步迭代器适合分页或流式数据,它们又应如何清理? 查看答案 ▾ 收起 ▴
异步可迭代对象暴露Symbol.asyncIterator,每次next调用都返回迭代结果的Promise。for await…of会逐项请求值,因此生成器可以随着消费进度才获取下一页,而不是先缓冲整个数据集。这形成自然的拉取式节奏,但本身并不保证全局内存上限。自身拥有的资源应在finally中清理。循环提前退出时,迭代器关闭会在可用时调用return,使生成器能够释放reader、cursor或连接。若底层I/O也需要取消,还应继续传递AbortSignal。
17 Streams API如何表达背压、所有权与取消? 查看答案 ▾ 收起 ▴
ReadableStream维护内部队列,并根据排队策略计算desired size。行为良好的底层数据源只在消费者有容量或收到pull请求时入队,而不会无限生产。获取reader会把流锁定给该reader;releaseLock只是释放锁,并不会取消数据源。cancel表示消费者不再需要数据,pipeTo则协调读取、写入、背压、关闭与错误传播。必须明确由谁拥有取消责任。对于大数据流,除非两个分支能以相近速度消费,否则应谨慎使用tee,以免慢分支造成额外缓冲。
18 switchMap、mergeMap、concatMap与exhaustMap分别编码了怎样的并发策略? 查看答案 ▾ 收起 ▴
switchMap在新的外层值到来时取消订阅上一个内层Observable,适合可被替代的搜索。mergeMap同时保留多个内层订阅,结果可能交错。concatMap把外层值排队并按顺序处理内层流。exhaustMap在当前内层流活跃时忽略新的外层值,可用于保护不可重复提交。关键边界是取消订阅,但只有Observable的teardown真正中止底层工作时,它才等于取消操作。应根据顺序、并发和取消语义选择,并测试完成与错误行为,而不是凭习惯选操作符。
37 Node.js中的process.nextTick、Promise reaction与事件循环阶段如何交互? 查看答案 ▾ 收起 ▴
JavaScript回调会完整运行,然后才处理两个延迟队列。Node先清空next-tick队列,再清空Promise微任务队列;新加入的条目也会被清空,之后才推进timers、poll、check等libuv阶段。这个优先级使process.nextTick适合狭窄的兼容边界,却不适合作为通用调度器:无限链会让I/O饥饿。Promise reaction与queueMicrotask更可移植,但同样可能使任务工作饥饿。还要说明执行上下文,因为ES module顶层执行本身位于微任务中,可能改变观察到的顺序。
38 为什么把CPU密集型JavaScript标成async仍不能避免阻塞事件循环? 查看答案 ▾ 收起 ▴
async只改变函数报告完成的方式,不会把函数体移到另一个线程。函数体仍会同步运行,直到await使它暂停;等待一个已经完成的计算,也无法追回事件循环线程此前被占用的时间。中等规模工作可以拆成经过测量的块,并通过任务级原语让出,使定时器和I/O获得执行机会。持续计算应使用worker thread,同时把消息传输成本纳入设计。还要在代表性负载下测量事件循环延迟和尾延迟,因为任意选择的工作块可能增加开销,也可能仍阻塞过久。
浏览器
6个问题 · 0 已看19 怎样实现事件委托,才能避免操作错误的元素? 查看答案 ▾ 收起 ▴
在稳定祖先元素上注册一个监听器,用event.target查找事件最初来自的节点;event.currentTarget始终是拥有监听器的祖先。由于目标可能是嵌套图标或包含文本的子元素,应使用closest匹配预期选择器,并确认匹配结果确实位于委托根内部后再操作。事件委托依赖传播机制;不冒泡事件、stopPropagation和Shadow DOM边界可能要求使用捕获阶段、更换事件,或在组件内部处理。选择器应表达语义,读取DOM数据后要验证,并在卸载时移除祖先监听器。
20 健壮的fetch包装器除了等待fetch本身,还必须处理什么? 查看答案 ▾ 收起 ▴
fetch通常只因网络级失败或中止而拒绝,不会仅因服务器返回404或500而拒绝,因此必须检查response.ok或明确接受的状态范围。响应体是流,通常只能消费一次;只有确实需要两个消费者时,才应在消费前clone。JSON解析可能独立于HTTP状态失败,错误中应保留响应上下文。用户取消、组件卸载和截止时间都应传入AbortSignal,并区分中止与业务失败。重试还必须定义幂等性、有界次数、退避和服务器指示,否则可能重复副作用。
21 JavaScript如何意外触发重复布局,范围最小的修复是什么? 查看答案 ▾ 收起 ▴
样式修改可能使布局信息失效。若循环先写入影响布局的样式,又立即读取getBoundingClientRect或offsetHeight等几何信息,浏览器为了返回当前值,可能在每轮同步更新样式与布局。这种读写交替就是布局抖动。范围最小的修复是先批量读取几何信息,在内存中计算,再批量写入;视觉更新常可安排到requestAnimationFrame。边界在于测量:并非所有DOM访问都会强制布局,合成友好的属性也可能避开布局,但提升图层并非免费,仍应使用性能工具验证。
22 IntersectionObserver会报告什么,又不应被用来保证什么? 查看答案 ▾ 收起 ▴
IntersectionObserver会异步报告目标元素相对root穿越所配置交叉阈值的情况,rootMargin会调整这个观察边界。每个entry提供几何信息、交叉比例和isIntersecting。它避免持续手动测量滚动位置,但回调不是每次像素移动的精确记录,送达时几何状态也可能再次改变。它适合懒加载、粗粒度可见状态或哨兵式分页。不要把它当作广告持续可见某段时间的证明;应结合时间累计和页面可见性规则,并在清理时unobserve目标。
23 为什么新安装的service worker可能无法控制已经打开的页面? 查看答案 ▾ 收起 ▴
Service worker具有install、waiting和activate阶段。更新成功后,只要旧worker仍控制着打开的客户端,新worker通常会等待,因此完成安装并不等于立即接管当前页面。skipWaiting与clients.claim可以加速接管,但可能让旧页面代码配上新缓存逻辑,必须把它设计成协调一致的版本迁移。应使用event.waitUntil延长安装或激活工作,明确设置缓存版本,并只删除应用自己拥有的缓存。缓存响应是需要失效策略的数据,不是所有请求的永久离线副本。
24 Web Component应如何跨越Shadow DOM边界定义公开状态与事件契约? 查看答案 ▾ 收起 ▴
丰富的JavaScript值应通过property暴露,可序列化的声明式配置适合attribute;二者是否互相反射必须明确设计,不能假设自动同步。observedAttributes与attributeChangedCallback应规范化输入,同时避免形成反射循环。Shadow DOM能隔离内部节点和许多样式,但不是安全边界。需要离开shadow tree的事件应使用CustomEvent,并按契约设置bubbles与composed,同时让detail保持精简且有文档。生命周期回调可能多次执行,因此connectedCallback中的设置必须具备幂等性,或在disconnectedCallback中成对清理。
Node运行时
6个问题 · 0 已看25 为什么不能死记Node.js中timer、immediate与微任务的一种全局顺序? 查看答案 ▾ 收起 ▴
Node会在事件循环的不同阶段执行回调,而Promise reaction与process.nextTick回调会在JavaScript回调周围的特定检查点清空。process.nextTick有独立的高优先级队列,递归填充时可能饿死I/O。setTimeout(…, 0) 与setImmediate的相对顺序取决于它们在何处被调度,以及外围是否处于I/O轮次,因此不存在脱离上下文的统一答案。回答时应先说明调度上下文,再追踪相关阶段和检查点。不要把任一API当作正确性锁;顺序应通过await、队列或显式状态转换表达。
26 Node的并发在什么情况下仍会阻塞事件循环,什么时候worker thread才有帮助? 查看答案 ▾ 收起 ▴
异步I/O让Node等待时不占用JavaScript线程,但JavaScript回调仍在事件循环线程上运行。长时间计算、超大解析或同步文件系统调用都会阻塞其他回调,与外围函数是否声明为async无关。libuv线程池只服务于特定原生操作,不会自动把任意JavaScript计算移过去。大量CPU密集型JavaScript可以使用worker thread,但必须值得承担消息传递和序列化开销。应先测量事件循环延迟、限制输入大小;若根因是无界数据处理,则优先考虑流式处理或更小批次。
27 Express中间件链应如何建立唯一且清晰的响应与错误边界? 查看答案 ▾ 收起 ▴
每个中间件都应在“结束响应”与“继续委托”之间选择其一,并在发送响应后停止控制流。响应后再调用next,或下游运行后再次发送,都会造成重复header错误和混乱的所有权。应在路由边界附近验证输入,让处理器职责集中,并把失败交给唯一的终结错误中间件;它以四参数签名区别于普通中间件。异步拒绝如何转发取决于Express版本和包装方式,因此必须明确约定并测试。最终处理器应映射已知运行错误、隐藏内部细节,并在header已发送后避免再次响应。
28 应如何选择NestJS provider的作用域,隐藏的可变状态会造成什么问题? 查看答案 ▾ 收起 ▴
无状态服务以及连接池等可安全共享的资源应使用默认singleton作用域。request scope为每个请求创建实例,并沿依赖provider传播,会增加分配和依赖图成本,因此只应用于真正绑定请求的状态。transient scope为每个消费者创建实例,只有在这种所有权是有意设计时才合适。singleton若保存“当前用户”或请求数据,会造成跨请求竞态与泄漏。应把请求值作为方法参数传递,或放入显式上下文抽象,并缩小模块export范围,让依赖所有权保持可见。
29 Deno权限控制如何支持最小权限,它的边界又在哪里? 查看答案 ▾ 收起 ▴
Deno默认不会获得广泛的文件、网络、环境变量、子进程等访问权,除非显式授予权限。应只授权所需的具体主机或目录,而不是使用全权限标志。程序可以查询或请求权限,但部署策略必须决定是否允许交互式提示。权限限制的是运行时能力;它不能证明导入代码可信,不能替代应用授权,也无法在访问已经获准后继续保护数据。还应审查依赖图,按项目策略固定并验证依赖,必要时拆分进程职责,并测试缺少权限时是否安全失败。
30 Electron的renderer与main process之间,怎样的IPC边界才安全? 查看答案 ▾ 收起 ▴
renderer发来的消息应视为不可信输入,尤其当页面可能显示远程或用户控制内容时。应启用context isolation,不暴露原始ipcRenderer或Node能力,而由preload脚本通过context bridge发布小型允许列表API。main process在执行特权操作前,必须验证channel、sender、参数形状、授权和目标资源。相比通用execute channel,应优先使用具有明确结果与错误schema的请求响应handler。导航、新窗口和外部URL打开还需要各自的允许列表。IPC隔离能减少能力暴露,但不能替代常规输入验证。
模式与工具链
6个问题 · 0 已看31 工厂模式何时能改善JavaScript设计,何时只是多余的间接层? 查看答案 ▾ 收起 ▴
当创建过程需要验证配置、选择不同实现、组装依赖,或在稳定接口后隐藏构造细节时,工厂很有价值。它为调用方提供单一策略边界,也便于测试注入替代实现,而无需了解具体构造函数。若工厂只是给一个简单类的new改名,没有新增约束或变化,就只是多余间接层。返回对象应具有明确的行为契约,不支持的类型要尽早失败,生命周期所有权必须清楚。若工厂创建socket、worker或订阅,返回抽象应暴露close或dispose,而不是隐藏清理责任。
32 观察者模式与发布订阅模式有何区别,它们又共享哪些运行风险? 查看答案 ▾ 收起 ▴
在直接观察者设计中,subject知道自己的observer集合并逐一通知。发布订阅则把broker或事件channel放在publisher与subscriber之间,减少直接认知,但更依赖清晰的事件契约和追踪能力。两者都必须明确订阅所有权、unsubscribe行为、重复监听策略,以及handler抛错时的规则。还要决定交付是同步、排队、有序还是并发,因为这些选择会影响重入和背压。不能让一个被遗忘的订阅使整个组件图保持可达;应返回取消句柄,并把卸载测试纳入契约。
33 怎样判断状态应属于本地、全局、派生、持久化,还是服务器状态? 查看答案 ▾ 收起 ▴
首先判断所有权与权威来源。状态应留在需要修改它的最小子树中。能够从现有状态计算出的值应保持派生关系,不要再保存一份需要同步的副本。只有多个远距离消费者确实需要协调写入时,才提升为全局状态。服务器状态具有远程权威、过期、重试和缓存失效问题,应使用查询缓存,而不是把它伪装成普通客户端状态。持久化是另一个边界,不是默认选择:必须给存储形状加版本,读取时验证,处理迁移与配额失败,也不要为了方便把秘密放入浏览器存储。
34 怎样让Jest异步测试因正确原因而稳定失败? 查看答案 ▾ 收起 ▴
测试必须return或await代表被测行为的Promise,否则断言执行前测试就可能结束。预期拒绝时,应await expect(promise).rejects,或在try/catch中配合断言次数,避免意外兑现静默通过。fake timer能控制计划时间,但Promise微任务和外部I/O可能需要根据所选计时器模式另行推进或mock。每个测试后都应恢复timer、spy和全局修改。优先测试可观察结果,而不是逐次调用的实现细节;每个测试还应拥有独立数据,避免顺序和并行执行影响结果。
35 为什么Playwright locator与web-first assertion比固定时长sleep更可靠? 查看答案 ▾ 收起 ▴
locator会在动作或断言执行时针对当前页面重新解析,Playwright还会等待相关可操作条件,例如可见、稳定以及能够接收事件。web-first assertion会重试,直到条件成功或超时。固定sleep只是猜测应用何时就绪:运行快时浪费时间,运行慢时仍会失败。应优先使用与用户可见契约关联的role、label或test-id locator,保持browser context隔离,并等待具体可观察状态。自动等待无法修复含糊的locator,也无法补救从未暴露就绪状态的应用。
36 Alpine.js中x-show与x-if有何区别,由此产生什么生命周期取舍? 查看答案 ▾ 收起 ▴
x-show会保留元素在DOM中,只切换其display状态,因此隐藏期间本地DOM状态以及已挂接的组件工作仍然存在。x-if通过template有条件地创建和移除DOM,可避免保留大型非活动子树,但会付出创建与销毁成本,并重置元素本地状态。选择依据应是生命周期语义,而不只是“是否可见”。频繁切换的轻量UI适合x-show,昂贵或语义上不存在的内容适合x-if。若初始隐藏内容可能在Alpine初始化前闪现,应同时使用x-cloak和对应CSS规则。
没有符合筛选条件的问题。