Proxy 与元编程

共 34 题
📑 题目列表 34 题
#
★★★

1. Proxy.revocable 临时凭证 在大型状态管理的设计取舍

Proxy.revocable 的临时凭证在大型状态管理中有何设计取舍?

  • revocable 创建可撤销代理
  • 撤销后访问抛 TypeError
  • 临时访问凭证、权限收回与失效策略

Proxy.revocable(target, handler) 返回 {proxy, revoke}:revoke 调用后代理变为"已撤销",此后任何操作(读取、写入、in、delete 等)都抛 TypeError,且不可恢复。这使其成为"临时凭证"机制:把受限的只读访问权以代理形式发放给第三方(插件、低权限模块、iframe 桥接),到期或权限变更时 revoke 一次性收回,比事后删除属性更彻底(代理拦截点直接失效)。大型状态管理中的设计取舍:临时凭证适合"限时/限权的状态视图"(如只读调试面板、外部工具访问、权限到期回收),结合 handler 中的校验可做到"谁、何时、读哪些字段"的审计;代价是代理与 revoke 的生命周期管理成本(忘记 revoke 造成失效困惑、代理逃逸后原始对象仍可被内部持有),且撤销后的 TypeError 需在调用方做防御处理。通常与"原始对象永不外泄 + 只暴露代理"的封装原则配合。

本题考察 revocable 的语义与权限设计。核心是"一次性失效的访问凭证"模型及其与权限收回场景的匹配,生命周期管理是工程重点;能说明代理逃逸风险与只暴露代理的封装原则即展示设计深度。

#
★★★

2. Object.hasOwn/Object.fromEntries/Object.groupBy 在数据变换的工程价值

Object.hasOwn、Object.fromEntries 与 Object.groupBy 在数据变换上有何工程价值?

  • hasOwn 替代 hasOwnProperty.call 的安全原因
  • fromEntries 的键值对数组反转
  • groupBy 的分组聚合语义

Object.hasOwn(obj, key) 是 hasOwnProperty 的标准替代:直接以对象为参数调用,避免了 obj.hasOwnProperty 被原型污染/遮蔽(如 obj 是 Object.create(null) 或覆盖了 hasOwnProperty)时的调用失败,语义是检查自有属性(不沿原型链)。Object.fromEntries(iterable) 把 [key, value] 键值对列表反向构造成对象,与 Object.entries 互逆,常用于 Map→对象、查询参数解析结果回构、过滤后的 entries 重组。Object.groupBy(items, cb)(ES2024)按回调返回的键把元素分组为对象:Object.groupBy 返回普通对象(键为字符串),Map.groupBy 返回 Map(键任意类型),取代手写 reduce 分组样板。工程价值:数据变换链(entries 过滤→fromEntries、groupBy 聚合)更声明式、更少出错;hasOwn 在遍历 for...in 时做自有属性过滤是高频用法。

本题考察对象数据变换 API 的现代写法。分别讲清三个 API 的语义与取代的旧写法(hasOwnProperty.call、reduce 分组),能说出 hasOwn 的安全动机与 Map.groupBy 的键类型优势即展示细节。

#
★★★

3. Symbol.metadata 与装饰器元数据在反射编程的应用

Symbol.metadata 与装饰器元数据如何在反射编程中应用?

  • Symbol.metadata 作为元数据挂载点
  • 装饰器通过 context.metadata 写入
  • 依赖注入、序列化等反射场景

Symbol.metadata 是装饰器元数据提案定义的内置 Symbol:每个类在构造时获得 [Symbol.metadata] 属性,指向一个按原型链继承的元数据对象;装饰器(Stage 3)的 context 参数带有 metadata 属性,装饰器可向其中写入键值(context.metadata[key] = value),且原型链上的装饰器元数据会合并到派生类的 metadata 中,形成可查询的反射信息。反射编程应用:依赖注入(装饰器标记依赖,运行时从元数据读取注入表)、ORM/序列化(字段装饰器登记属性名与类型映射)、路由声明(方法装饰器登记 path/method)、校验规则注册,框架在运行时统一读取 [Symbol.metadata] 驱动行为,替代手写注册表或命名约定。注意:元数据是构建期/装饰器执行期写入的静态信息,适合声明式配置而非运行时可变状态;当前需 polyfill 与编译支持(Babel/TS 需配置)。

本题考察装饰器元数据的反射模型。核心是"context.metadata 写入 + Symbol.metadata 挂载 + 原型链合并"的三层结构,落到 DI/序列化等反射场景;能说明元数据与运行时状态的区别即展示边界理解。

#
★★★

4. Proxy trap 的性能开销与回退判断

Proxy trap 的性能开销来自哪里?何时应回退到非代理实现?

  • 每次操作都经过 trap 分发
  • 代理对象无法享受形状优化
  • 热点路径与只读数据的回退策略

Proxy 的每次属性访问、写入、方法调用都需经过 trap 函数分发,且代理对象的隐藏类(Map)不稳定、无法被 V8 的 IC/形状优化覆盖,因此访问开销显著高于普通对象(普通字段访问是内存直读,代理每次都要查 trap 表并调用函数)。开销还随 trap 内逻辑增长(如响应式依赖收集、递归代理)。回退判断:热点路径(渲染循环、高频数据结构操作)中代理开销会累积成可感知的延迟;对"只读配置、静态常量、低频访问"场景无需代理;能内联到数据层(如直接用数组/Map 表达)就不套 Proxy;大对象全量代理(深递归)会放大分配与 GC 压力。工程上:Vue 3 只在组件数据的读取/写入路径上代理(且用懒递归),对不参与响应式的大块数据(如二进制、长列表原始数据)建议保持普通对象;性能敏感库可提供"非代理模式"开关。

本题考察代理的运行时代价。核心是"trap 分发 + 形状优化失效"两类开销来源,回退策略按"访问频率、数据规模、可变性"决策;能举出 Vue 3 的懒递归代理与数据分流实践即展示工程认知。

#
★★★

5. Proxy 的 13 个拦截陷阱(trap)在 Vue 3 响应式与 MobX 可观察对象中的最小集取舍

Proxy 的 13 个陷阱在 Vue 3 响应式与 MobX 可观察对象中如何取舍最小集?

  • 13 个 trap 的职责分组
  • Vue 3 使用的 get/set/has/ownKeys/deleteProperty 核心集
  • 未拦截 trap 的默认转发语义

Proxy 共 13 个 trap:get、set、has、deleteProperty、ownKeys、getOwnPropertyDescriptor、defineProperty、getPrototypeOf、setPrototypeOf、isExtensible、preventExtensions、apply、construct。Vue 3 的 reactive 核心只实现 get(依赖收集)、set(触发更新)、has(in 运算符收集)、ownKeys(迭代收集)、deleteProperty(删除触发)——这是"读取/写入/存在性/枚举/删除"五个基础操作的最小集,配合懒递归(访问到嵌套对象才代理)与数组特殊处理(length 语义);MobX 的 observable 类似,但更强调 get/set 与深观察策略。取舍依据:未被拦截的 trap(如 apply/construct/getPrototypeOf)走 Reflect 默认转发,语义不变,除非需要函数代理、原型劫持等高级能力否则不必实现;ownKeys 与 getOwnPropertyDescriptor 需成对实现(invariant 要求一致),defineProperty 与 set 需要协同保证响应式一致性。

本题考察响应式框架的代理设计。核心是"读取/写入/存在/枚举/删除"五操作与其余 trap 的默认转发,成对实现与 invariant 一致性是易错点;能说明懒递归与数组语义即展示框架级理解。

#
★★★

6. Proxy 的 get trap 中如何实现负索引、链式调用与链式代理的拦截

Proxy 的 get trap 如何实现负索引、链式调用与链式代理?

  • get trap 对属性名的改写(负索引映射)
  • 返回包装函数实现链式调用
  • 每次访问返回新代理的链式代理

get trap 收到属性名(含 symbol),可在返回前改写语义:负索引场景,把 "-1" 等数字字符串解析后映射为 length+index(arr[-1] 返回末尾元素),同时配合 has/ownKeys 保持一致(如 -1 in arr 与 Object.keys 不泄露负键)。链式调用:get trap 在访问"方法名"时返回包装函数,包装函数内部执行目标操作并返回 this(或新上下文),实现 a.query().where(...).get() 的 DSL 链。链式代理:访问任意属性都返回一个新的代理对象(每个属性都是代理),形成按需创建的嵌套代理树,用于深度数据访问控制、虚拟对象图(如 Mock 任意路径、schema 驱动对象);需管理代理缓存(同一路径复用同一代理)与惰性创建(避免全量递归)。注意 get trap 中 this 绑定与 Reflect.get 的使用,避免丢失接收者。

本题考察 get trap 的三种编程模式。分别对应"语义改写(负索引)、行为包装(链式 DSL)、结构生成(链式代理)",缓存与一致性(has/ownKeys 配合)是工程细节;能说明 Reflect.get 与 receiver 的关系即展示规范掌握。

#
★★★

7. Reflect API 与 Proxy 的关系(Reflect.get 对应 [[Get]] 内部方法)在元编程时的工程价值

Reflect API 与 Proxy 的关系是什么?元编程时 Reflect 有何工程价值?

  • Reflect 方法对应对象内部方法
  • trap 内用 Reflect 转发保持默认语义
  • 函数化调用与返回布尔值的改进

Reflect 的方法集合与 JS 对象的内部方法一一对应:Reflect.get 对应 [[Get]]、Reflect.set 对应 [[Set]]、Reflect.ownKeys 对应 [[OwnPropertyKeys]]、Reflect.apply 对应 [[Call]] 等,是"以函数形式访问内部方法"的标准化入口。与 Proxy 的关系:Proxy trap 的默认行为就是用同名 Reflect 方法转发,因此 trap 内调用 Reflect 方法可精确复刻"没有代理时的行为"(含 receiver 传递、原型链查找、invariant 检查),是避免手写转发遗漏的可靠方式。工程价值:Reflect.set/deleteProperty 返回布尔值(替代赋值表达式的静默失败)便于校验;Reflect.apply/construct 让函数与构造调用可被动态执行与传递 this;Reflect.getOwnPropertyDescriptor 等提供无异常抛出的一致性;在元编程(代理转发、函数装饰、属性操作统一化)中作为"标准操作语义"的载体。

本题考察 Reflect 的定位。核心是"内部方法→函数化 API"与"trap 默认行为=Reflect 转发"的关系,布尔返回值与 receiver 传递是细节考点;能说明为何 trap 内必须用 Reflect 转发(保持不变量与默认语义)即展示理解。

#
★★★

8. Reflect.apply/Reflect.construct 与 Function.prototype.apply/call 的语义差异与历史包袱

Reflect.apply/Reflect.construct 与 Function.prototype.apply/call 有何语义差异?

  • apply/call 是 Function.prototype 上的方法
  • Reflect.apply 更函数式、统一参数形态
  • Reflect.construct 支持 newTarget 参数

语义上 Function.prototype.apply/call 与 Reflect.apply 都能以指定 this 调用函数:apply 接收参数数组、call 接收参数列表、Reflect.apply(fn, thisArg, argsArray) 统一为 (函数, this, 参数数组) 形态,且不依赖函数的 prototype 链(对没有 apply 方法的函数对象或 Proxy 包装的函数同样可用)。历史包袱:apply/call 挂在 Function.prototype,若函数对象的 apply 被覆盖/污染或函数来自其他 Realm(apply 被重定义),调用 fn.apply 会失败或行为异常,Reflect.apply 直接按内部方法调用规避了该问题;Reflect.construct(target, args, newTarget) 额外支持指定 newTarget(控制实例原型来自哪个构造函数的 prototype),这是 apply 无法表达的(构造调用需 new 关键字或 Reflect.construct)。工程上:动态调用统一用 Reflect.apply,需要控制实例原型/拦截构造用 Reflect.construct,函数装饰器与 Proxy apply trap 内转发也应走 Reflect。

本题考察调用语义的标准化演进。核心是"统一形态 + 摆脱 prototype 依赖 + 支持 newTarget"三点,Realm 边界与 apply 被覆盖是历史包袱的具体体现;能说出 Reflect.construct 的 newTarget 用途即展示规范深度。

#
★★★

9. Proxy 在对象不可变封装(Object.freeze 与 Proxy 包装)

Proxy 如何与 Object.freeze 协作实现对象不可变封装?边界在哪里?

  • freeze 的静态不可变 vs Proxy 的动态拦截
  • 冻结目标上的 set 拦截与 invariant
  • 防御式深冻结与代理组合

Object.freeze 是静态封装:属性在冻结时固化(不可写、不可配置),之后任何写入在严格模式抛错、非严格模式静默失败,但嵌套对象仍可变且结构在冻结时确定;Proxy 是动态封装:set/defineProperty 等 trap 可在运行时决定拒绝(返回 false 或抛错),并支持按条件放行(如只读视图、仅允许特定字段)。组合使用时需注意不变量:若目标是冻结的(isExtensible 为 false 且属性不可写),Proxy 的 set trap 即使返回 true 也会因 invariant 检查失败抛 TypeError(trap 不能违背目标不可变性),因此"冻结目标 + 放行写入"的代理组合无效。工程实践:防御式不可变用"深 freeze(递归)+ 严格模式 + 类型层面只读(TS readonly)",运行时行为控制用 Proxy(set 拒绝)而不依赖 freeze;性能敏感处优先 freeze(无代理开销),需要逻辑拦截再考虑 Proxy。

本题考察不可变封装的两种机制。核心是"静态 freeze vs 动态 Proxy"的定位与"冻结目标上的 set trap 受 invariant 约束"的边界,能说出深冻结与严格模式的配合即展示工程完整性。

#
★★★

10. Reflect.ownKeys/Reflect.getPrototypeOf 与对应 Object.* API 的能力差异

Reflect.ownKeys/Reflect.getPrototypeOf 与对应 Object.* API 有何能力差异?

  • ownKeys 含 Symbol 键、getPrototypeOf 无异常
  • Object.getOwnPropertyNames 的取舍
  • 异常行为与返回值差异

差异主要在"返回集合的完整度"与"异常行为":Reflect.ownKeys(obj) 返回自有键的完整集合(字符串键 + Symbol 键),而 Object.getOwnPropertyNames 只返回字符串键、Object.keys 只返回可枚举字符串键——要拿全键必须组合或用 Reflect.ownKeys;Reflect.getPrototypeOf 与 Object.getPrototypeOf 语义一致,但 Reflect 版本按内部方法操作,对非对象参数直接抛 TypeError,Object 版本则先做 ToObject 装箱(如 Object.getPrototypeOf(1) 返回 Number.prototype),Reflect.getPrototypeOf(1) 抛错。同类差异贯穿:Reflect.set/deleteProperty 返回布尔值而非静默失败,Reflect.defineProperty 返回布尔值而 Object.defineProperty 抛异常。工程上:反射式遍历用 Reflect.ownKeys(含 Symbol),需要严格类型校验的反射操作用 Reflect 系列,兼容宽松路径用 Object 系列。

本题考察两套反射 API 的差异边界。核心是"ownKeys 含 Symbol 与 getPrototypeOf 不做装箱"两点,布尔返回 vs 抛异常是另一维度;能说明 ToObject 装箱的差异即展示规范细节掌握。

#
★★★

11. Proxy 的 invariant(不变量)在实现响应式时的边界与陷阱

Proxy 的 trap 不变量(invariant)在实现响应式时有哪些边界与陷阱?

  • 不变量约束 trap 返回值的合法性
  • 不可配置属性与不可扩展目标约束
  • 响应式代理中的典型违规场景

Proxy 的每个 trap 都有不变量(invariant):trap 的返回值必须与目标对象的真实状态一致,否则抛 TypeError。典型约束:get trap 对目标不可配置且不可写的自有数据属性必须返回其真实值(否则违规);has trap 对目标不可配置的自有属性必须返回 true;getOwnPropertyDescriptor 不能把不可配置属性描述为可配置、不能把不存在的属性描述为存在;ownKeys 的返回集合必须包含所有不可配置自有键且不能包含额外键(除非目标可扩展)。响应式实现中的陷阱:在 get trap 中"改写返回值"(如包装成 computed 对象)时若目标是不可配置/只读的会抛错;set trap 对不可配置只读属性放行写入会违规;ownKeys 返回了额外虚拟键(如为迭代收集添加标记)但目标不可扩展时违规。对策:代理目标保持可扩展与可写、虚拟属性需配合 getOwnPropertyDescriptor 且不触碰不可配置属性、用 defineProperty 先声明再返回。

本题考察代理约束的规范细节。核心是"trap 返回值必须与目标真实状态一致"的总原则,典型违规场景(只读属性改写、虚拟键、描述符不一致)是考点;能说明 Vue 3 对数组 length 等不可配置属性的特殊处理即展示实战认知。

#
★★★

12. Proxy 在响应式系统(Vue 3)与数据校验的应用

Proxy 在响应式系统(Vue 3)与数据校验中分别如何应用?

  • Vue 3 reactive 的依赖收集与触发更新
  • 数据校验的 set 拦截与写保护
  • 深代理、数组与边界处理

Vue 3 的 reactive 用 Proxy 实现响应式:读取属性时(get trap)收集当前副作用(依赖),写入时(set trap)触发更新;对象属性是嵌套对象时懒递归代理(访问到才代理),数组通过拦截索引读写与 length 保持响应式,has/ownKeys 让 in 与迭代也收集依赖,deleteProperty 触发更新。与 Vue 2 的 defineProperty 相比,Proxy 天然支持新增属性、数组索引、Map/Set 等,且不预设键集合。数据校验场景:set trap 在写入前校验(类型、范围、业务规则),不合法返回 false(非严格)或抛错(严格),实现"写入即校验"的守卫式 API;配合 get trap 做只读视图(对外暴露只读代理)、日志审计(记录每次读写)。注意校验代理不要包在响应式代理外层造成多层开销,且校验规则应集中在 schema/工厂函数中便于维护。

本题考察代理的两类核心应用。响应式侧讲清"读收集、写触发、懒递归、数组语义",校验侧讲"set 守卫与只读视图",能对比 Vue 2/3 的实现差异即展示框架演进认知。

#
★★★

13. Proxy 与 Reflect 的 trap 不变量与边界

Proxy 与 Reflect 的 trap 不变量及边界有哪些?如何规避?

  • 不变量是 trap 与目标的一致性约束
  • Reflect 转发默认触发同样检查
  • 边界场景与规避策略

Proxy 的每个 trap 都受不变量约束:trap 的返回值必须与目标对象的实际状态相容(不可配置属性、不可扩展目标、原型链、描述符一致性等),违背时引擎抛 TypeError——这是"代理不能歪曲目标基本事实"的防线。Reflect 方法在 trap 内转发时会执行同样的内部检查:Reflect.get 在目标不可配置只读属性上返回不同值会抛错、Reflect.ownKeys 返回与实际不符会抛错,因此"用 Reflect 转发"天然遵守不变量。边界场景:为"虚拟属性"(目标不存在但代理要暴露)实现 get 时,需同时实现 has 与 getOwnPropertyDescriptor 保持一致且不能伪装不可配置属性;对不可扩展目标不能声明多余键;函数代理的 apply/construct 需目标可调用。规避策略:代理目标保持"简单可扩展可写",复杂语义(虚拟字段、权限)用额外数据结构记录并保证各 trap 口径一致,必要时先 defineProperty 声明再提供。

本题考察代理正确性的约束体系。核心是"trap 返回值 vs 目标真实状态"的一致性总原则与 Reflect 转发的合规性,虚拟属性是高频边界场景;能给出"多 trap 口径一致"的工程清单即展示规范掌握。

#
★★★

14. Proxy 陷阱触发顺序(get/has/defineProperty)

Proxy 的陷阱触发顺序(get/has/defineProperty)是怎样的?哪些操作会触发多个 trap?

  • 单操作与多 trap 组合(如 in、解构)
  • 操作内部的默认流程顺序
  • 顺序对响应式收集的影响

多数操作触发单一 trap:属性读取触发 get、in 触发 has、delete 触发 deleteProperty、Object.getOwnPropertyDescriptor 触发 getOwnPropertyDescriptor。组合操作会按内部方法流程依次触发多个 trap:Object.assign 读取源属性触发 get、写目标触发 set;展开运算符 {...obj} 触发 ownKeys + getOwnPropertyDescriptor + get;解构触发 has(判断存在)+ get(取值);for...in 触发 ownKeys + getOwnPropertyDescriptor(过滤可枚举)+ get。顺序细节:Object.keys 先 ownKeys 再逐键 getOwnPropertyDescriptor;in 操作对代理先触发 has trap,若返回 true 且目标为不可配置自有属性则无需再查原型。响应式场景中该顺序决定依赖收集覆盖面:Vue 3 通过 ownKeys 收集"迭代依赖"、get 收集"取值依赖",理解触发链才能解释 for...of 与解构为何能触发更新。

本题考察操作与 trap 的映射关系。核心是"单操作单 trap、组合操作多 trap"以及 ownKeys→getOwnPropertyDescriptor→get 的典型链条;能结合响应式依赖收集说明顺序的实际影响即展示应用深度。

#
★★★

15. Proxy 的 revoke(Proxy.revocable)在临时凭证场景的应用

Proxy.revocable 在临时凭证场景中有哪些应用?撤销机制如何设计?

  • 一次性授权的发放与收回
  • 撤销后操作抛错的行为
  • 过期、权限变更与审计场景

Proxy.revocable(target, handler) 返回可撤销代理与 revoke 函数:revoke() 后代理所有操作抛 TypeError,且无法恢复,天然契合"临时凭证"模型。应用场景:向第三方/低权限代码发放"限时只读视图"(访问代理,到期 revoke);插件系统在卸载时撤销其持有的状态引用;iframe/Worker 桥接中的受限访问令牌;权限变更后立即收回已发放的引用(无需等待引用清零)。设计要点:代理与 revoke 配对管理(Map 记录发放对象与撤销函数);撤销前触发清理钩子(如日志审计"凭证于何时撤销");调用方需防御撤销后的 TypeError(try/catch 或先检查);"过期"可结合定时器调用 revoke,"权限降级"可结合二次代理(内层校验)。注意:revoke 只影响该代理,target 仍可被内部持有,封装原则是"只外发代理"。

本题考察临时凭证的工程化设计。核心是"发放-使用-撤销"的生命周期与失效即抛错的契约语义,配对管理与防御性调用是工程要点;能结合插件/桥接场景给出完整设计即展示架构能力。

#
★★★

16. Object.hasOwn() 与 Object.prototype.hasOwnProperty.call() 的安全差异

Object.hasOwn() 与 Object.prototype.hasOwnProperty.call() 相比有哪些安全差异?

  • hasOwnProperty 被遮蔽/覆盖的风险
  • Object.create(null) 的对象调用问题
  • hasOwn 的规范保证与性能

老写法 obj.hasOwnProperty(key) 的问题:若 obj 自身或原型链上定义了 hasOwnProperty 属性(数据或方法覆盖),调用会失败或行为异常;Object.create(null) 的对象没有该原型方法直接报错。因此工程上常用 Object.prototype.hasOwnProperty.call(obj, key) 规避——通过 call 借用原型方法,但写法冗长且 call 本身在极端环境也可能被篡改。Object.hasOwn(obj, key)(ES2022)是标准内置函数:直接检查 obj 的自有属性(内部 [[HasOwnProperty]]),不经过对象上的同名属性、不沿原型链、对任何对象(含 null 原型)都安全,且由引擎原生实现性能更好。安全差异的本质:hasOwn 把"自有属性检查"从"可被对象状态影响的实例方法"提升为"引擎级的纯函数"。工程上所有自有属性判断应统一用 Object.hasOwn。

本题考察 API 演进的安全动机。核心是"原型方法可被遮蔽 vs 内置函数不可干扰"的差异,null 原型对象是典型场景;能解释 call 借用的历史妥协与 hasOwn 的规范化意义即展示理解。

#
★★

17. Proxy 性能开销(每次属性访问的拦截调用)在热点路径的取舍与不可代理的内置 slot(如 Date)

Proxy 的性能开销在热点路径如何取舍?为何 Date 等内置对象不可代理?

  • 每次操作经 trap 分发的开销累积
  • 内置对象依赖内部槽(internal slot)存储状态
  • 代理内置对象抛 TypeError 的机制

Proxy 的性能开销表现为每次属性访问/方法调用都要经 trap 分发且无法享受形状优化(隐藏类/IC 失效),热点路径(高频循环、每帧更新的数据结构、大规模响应式数据)上开销会线性放大,需要评估:低频访问或一次性配置可用 Proxy 换取灵活性,高频路径应内联逻辑或退化为普通对象/数组。不可代理的内置对象:Date、Map、Set、Promise、RegExp 等的实例依赖内部槽(如 Date 的 [[DateValue]]、Map 的内部存储)保存状态,内部方法直接读写这些槽而不经过属性查找;Proxy 的 get/set trap 无法模拟内部槽,因此对这类对象做 get/set 拦截后,其内部方法(如 date.getTime()、map.set)在代理上执行会因"无内部槽"抛 TypeError(规范要求:代理的 trap 只负责属性语义,内部方法访问内部槽必须命中真实目标,而目标是 Proxy 时其内部槽不可见)。工程上:包装这类对象应"组合"(外层持有真实实例并转发方法)而非"代理"。

本题考察代理的两类边界。性能侧讲清开销来源与热点路径决策,内置对象侧讲清"内部槽不可代理"的规范机制;能说明组合替代代理的实践即展示工程解法。

#
★★

18. Proxy 与 Vue 3 的 reactive() 在嵌套对象与数组的代理回收与 lazy 初始化的现代实现

Vue 3 的 reactive() 如何在嵌套对象与数组上实现代理回收与 lazy 初始化?

  • 懒递归:访问到才代理嵌套对象
  • WeakMap 缓存避免重复代理与代理回收
  • 数组索引/length 的响应式处理

Vue 3 的 reactive 采用懒递归:顶层对象立即代理,嵌套对象在 get trap 中首次被读取时才被代理并缓存(读一个、代理一个),避免全量深代理的启动开销;代理结果存入 WeakMap(原始对象→代理)与反查表(代理→原始对象),保证同一原始对象始终返回同一代理(引用稳定性,组件依赖 identity 判断更新)。代理回收:由于 WeakMap 弱引用原始对象,原始对象不可达时缓存条目随 GC 回收,代理不会成为额外强引用造成泄漏;reactive 还区分"已代理/原始/只读/浅响应式"状态(标记位),浅响应式(shallowReactive)跳过深层。数组处理:拦截索引读写、push/pop 等方法与 length 属性,通过 effect 的迭代收集使 for...of、includes 等也具备响应性。工程上理解"缓存命中 + 懒递归"能解释为何直接访问 reactive 返回原对象会丢失响应性。

本题考察响应式实现的现代细节。核心是"懒递归 + WeakMap 双缓存 + 数组语义"三个机制及其对性能与引用稳定性的作用;能说明 raw/reactive 的反查与 shallow 变体即展示框架级认知。

#
★★

19. Symbol 类型作为对象唯一属性的工程价值(避免命名冲突、定义协议语义)

Symbol 作为对象唯一属性键有哪些工程价值?

  • 全局唯一性避免命名冲突
  • 非枚举性隐藏内部元数据
  • 协议定义与库间协作

Symbol 属性键的工程价值:唯一性——Symbol() 每次生成全局唯一值,多个库/模块各自使用自己的 Symbol 键不会互相覆盖,可用于扩展第三方对象(打补丁、挂元数据)而无需担心键冲突;非枚举性——Symbol 键不进入 for...in、Object.keys 与 JSON.stringify,适合存放"内部元数据"(缓存、标记、配置)而不污染公共视图;协议化——内置与自定义 Symbol 定义语义契约(Symbol.iterator 可迭代、Symbol.toPrimitive 转换行为、Symbol.for 跨模块共享协议键),框架可用 Symbol 键暴露扩展点。工程实践:为对象附加"隐藏状态"用 Symbol(配合 Object.getOwnPropertySymbols 读取);模块间共享协议用 Symbol.for;避免用字符串魔法键做库扩展。注意:Symbol 键仍可经 Reflect.ownKeys 发现,不是安全机制;访问器可枚举性按描述符决定。

本题考察 Symbol 键的三重价值。核心是"唯一(防冲突)、隐藏(不枚举)、协议(语义契约)",分别对应扩展安全、元数据、库设计三类场景;能说明 Reflect.ownKeys 仍可发现 Symbol 的边界即展示客观认知。

#
★★

20. Symbol.iterator/Symbol.asyncIterator 在自定义可迭代对象与 for-await-of 的协议实现

如何通过 Symbol.iterator/Symbol.asyncIterator 实现自定义可迭代对象?与 for await...of 如何协作?

  • 可迭代协议与迭代器协议的实现
  • 同步/异步迭代器的双协议
  • 返回协议(return)与提前退出

实现可迭代对象需提供 Symbol.iterator 方法返回迭代器(含 next() 方法,返回 {value, done},可选 return/throw):手写时用闭包保存游标状态,或用生成器函数(function*)作为方法直接获得符合协议的迭代器;for...of 与展开/解构会自动调用该协议。异步版本 [Symbol.asyncIterator] 返回 next 返回 Promise 的迭代器(async generator 天然实现),for await...of 消费:循环等待每个 next 的 Promise 兑现,处理 value 直至 done。协作要点:提前退出(break/return/异常)时引擎调用迭代器的 return()(若存在),用于清理(关闭流、取消订阅);迭代器应处理 done 后的多余调用(返回 {done:true});可迭代对象自身与返回的迭代器可分离(迭代器模式)。工程上:包装数据源(数据库游标、WebSocket 消息流、分页)为可迭代对象可统一消费语法。

本题考察迭代协议的实现规范。核心是"[Symbol.iterator] 返回含 next 的迭代器"与 async 版本的 Promise 化差异,return 协议与清理是易漏点;能给出生成器一行实现即展示实践熟练度。

#
★★

21. Generator Function(function*)的暂停-恢复机制在异步流程控制与状态机的应用

Generator Function 的暂停-恢复机制如何应用于异步流程控制与状态机?

  • yield 的暂停与 next 的恢复
  • 协程式异步流程(co 风格)
  • 生成器表达状态机

生成器函数(function*)执行到 yield 时暂停并交出值,外部调用 next(value) 恢复并把 value 作为 yield 表达式的值注入,形成双向通信的协程;每次调用生成器函数都创建独立迭代器实例,状态由暂停位置隐式保存。异步流程控制:co 风格运行时把生成器当作"async/await 前身"——yield 一个 Promise,运行时 await 它并把结果 next 回去(yield 的"返回值"),错误则 throw 进生成器,实现同步写法的异步编排(历史库 co、早期 redux-saga 的 effect 描述式流程);现代直接使用 async/await,但 redux-saga 的"可测试性"(yield 纯对象、由中间件解释)仍是生成器异步的价值场景。状态机:用生成器把状态流转写成顺序代码,yield 作为状态边界(每个 yield 是一个可中断/恢复的转折点),配合 next 的注入值驱动转移,天然表达有限状态机的步进语义。

本题考察生成器的双领域价值。异步侧讲清"yield Promise + 运行时驱动"的协程模型及其与 async/await 的演进关系,状态机侧讲"yield 即状态边界";能说明 saga 的 effect 可测试性设计即展示深度。

#
★★

22. Generator 的 yield* 委托迭代在递归遍历树形结构与组合迭代器的工程实践

yield* 委托迭代如何用于递归遍历树形结构与组合迭代器?

  • yield* 把迭代控制权委托给子迭代器
  • 递归遍历的扁平化输出
  • 多迭代器组合与返回值的转发

yield* iterable 把生成器的迭代控制权委托给 iterable:子迭代器的每个产出值逐一遍历转发给外部调用者,子迭代器完成(done)后 yield* 表达式的值为其返回值;委托期间外部对迭代器的 return()/throw() 也会转发到子迭代器,保证清理与错误传播。递归遍历:树形结构(DOM、组件树、文件系统)用递归生成器 + yield* 实现深度优先扁平化输出——函数递归调用自身并 yield* 其结果,调用者以线性 for...of 消费整棵树,无需显式维护栈。组合迭代器:用 yield* 拼接多个来源(数组、其他生成器、流)形成统一序列;条件分支(yield* a 或 yield* b)按逻辑切换来源。工程实践:分页游标的逐页展开、事件流的过滤转换链、遍历器库的内部实现都依赖 yield*;注意性能:深递归委托在每层有少量迭代器对象开销,超深结构考虑显式栈迭代。

本题考察委托迭代的递归能力。核心是"yield* 转发控制权与返回值、清理转发"的语义,递归树的扁平化是典型应用;能说明 return/throw 的转发机制即展示协议级理解。

#
★★

23. Async Generator(async function*)与 Async Iterator 在 Node.js 流与 Web Streams 的协作

Async Generator 与 Async Iterator 如何与 Node.js 流、Web Streams 协作?

  • 流对象转为异步迭代器
  • for await...of 消费流
  • 背压与取消的传播

Node.js 的 Readable 流原生实现 [Symbol.asyncIterator],可直接 for await (const chunk of readable) 消费;Web Streams 的 ReadableStream 同样提供异步迭代支持(getReader 手动读取或 [Symbol.asyncIterator] 直接遍历),两者都让"流式数据处理"获得统一的消费语法。Async Generator 作为流的"生产端":内部从流中读取、转换、yield 产出(如逐行处理、chunk 聚合),或反向把生成的值写入流;配合 TransformStream 可构建可组合的数据管道(读→转换→写)。协作要点:背压——for await...of 逐段消费天然放慢读取(读完一块再取下一块),消费慢则数据在缓冲中累积,流级背压由底层实现传递;取消——循环提前退出会调用迭代器的 return(),Node 流与 Web 流会相应销毁/取消底层资源(destroy/cancel)。工程实践:日志流分析、CSV 流式解析、WebSocket 消息流包装为 async 迭代器统一业务代码形态。

本题考察流与异步迭代的互通。核心是"双端流的 async 迭代协议"与背压/取消的协作语义,能说明 return() 触发底层 cancel 即展示边界理解;给出流式处理场景即完整。

#
★★

24. Proxy 的 ownKeys/getOwnPropertyDescriptor 拦截在不可枚举属性模拟的工程应用

Proxy 的 ownKeys/getOwnPropertyDescriptor 拦截如何实现不可枚举属性模拟?

  • ownKeys 控制键集合
  • getOwnPropertyDescriptor 控制可枚举性
  • 虚拟私有属性与 API 表面控制

ownKeys trap 决定 Object.keys/for...in/Reflect.ownKeys 看到的键集合,getOwnPropertyDescriptor trap 决定每个键的描述符(可枚举性、可配置性等),二者组合可模拟"不可枚举/虚拟"属性:在 ownKeys 中返回目标键加虚拟键,在 getOwnPropertyDescriptor 中为虚拟键返回 enumerable:false 的描述符(Object.keys 与 for...in 会过滤掉),使属性"存在但不可枚举"——类似内置对象的非枚举方法(如数组的 length、对象的 toString)。工程应用:库在对象表面暴露 API 时隐藏内部状态(不可枚举的内部字段),防止 JSON.stringify/for...in 泄漏;实现"只读虚拟属性"(get trap 提供值、描述符声明为只读);兼容旧接口的"隐藏实现"(如模拟 Symbol 之前的内部标识)。注意不变量:不可配置的现有属性描述符必须真实返回,虚拟键不能与不可扩展目标的实际键冲突;配合 has trap 使 in 运算保持一致。

本题考察枚举控制类 trap 的配合。核心是"ownKeys 定集合 + getOwnPropertyDescriptor 定枚举性"的双 trap 协作与不可枚举模拟,不变量与 in 一致性是细节考点;能结合 JSON.stringify 泄漏场景说明价值即完整。

#
★★

25. Reflect.ownKeys 与 Object.keys 在 Symbol 与不可枚举属性枚举的差异

Reflect.ownKeys 与 Object.keys 在 Symbol 键与不可枚举属性上有何差异?

  • ownKeys 返回全部自有键
  • Object.keys 只返回可枚举字符串键
  • 遍历策略的组合使用

Reflect.ownKeys(obj) 返回对象全部自有键的规范顺序集合:字符串键(整数序在前、其余按创建序)+ Symbol 键,包含不可枚举属性与不可配置属性;Object.keys(obj) 只返回"可枚举的字符串键",不含 Symbol 键与不可枚举属性;Object.getOwnPropertyNames 返回全部字符串键(含不可枚举);Object.getOwnPropertySymbols 只返回 Symbol 键。差异的工程影响:遍历对象"真实全貌"用 Reflect.ownKeys(如代理 ownKeys trap 的标准转发、对比两个对象的所有自有属性);业务迭代/序列化用 Object.keys(只关心可枚举数据);需要按类型分别处理时组合 Object.getOwnPropertyNames 与 getOwnPropertySymbols。注意:for...in 还会沿原型链枚举(与前三者不同),且顺序规则由规范定义(整数键升序优先)——依赖键顺序的序列化需明确使用 Reflect.ownKeys 并自行排序。

本题考察键枚举 API 的矩阵差异。核心是"全量 vs 可枚举字符串 vs 字符串全量 vs Symbol 专属"四个集合的边界,顺序规则是细节;能给出遍历场景的选型即展示实用掌握。

#
★★

26. Proxy 的 has trap(in 操作符)在权限校验与 in 表达式拦截的现代工程价值

Proxy 的 has trap 如何拦截 in 操作符?在权限校验上有何工程价值?

  • has trap 对应 in 的语义
  • 权限校验(白名单/黑名单)
  • 与 get 的一致性与不变量

has trap 拦截 in 操作符与 Reflect.has:对代理执行 key in proxy 时,引擎调用 has trap,返回 true/false 决定"key 是否存在"(不再沿原型链查找)。权限校验价值:把"是否有权访问该字段"建模为"字段是否存在"——无权限的键返回 false,调用方代码无需改动即被挡住(如隐藏敏感配置、隔离沙箱内对象的私有数据);配合 get trap 返回 undefined 形成一致性(has false 时 get 返回 undefined,in 与取值结果对齐)。现代工程场景:受控数据网关(按角色暴露字段子集)、插件 API 面裁剪(只暴露白名单方法)、测试 mock 的字段存在性控制。注意不变量:目标为不可配置自有属性时 has 必须返回 true;has 与 get/ownKeys 的口径应一致(虚拟字段若 has true 则 get 需有值、ownKeys 需含该键),避免"in 说存在取值却没有"的怪异语义。

本题考察 has trap 的语义与应用。核心是"has 拦截存在性判断"以及"存在性=权限"的建模思路,多 trap 口径一致是工程要点;能给出角色裁剪的示例即展示落地能力。

#
★★

27. Symbol.for 与 Symbol 的全局注册表(Symbol 注册池)

Symbol.for 与全局注册表(Symbol 注册池)的工作机制是什么?

  • 注册表按 key 存储符号
  • Symbol.for/Symbol.keyFor 的查询与创建
  • 跨 Realm 共享与滥用风险

全局符号注册表是引擎级的字符串→Symbol 映射:Symbol.for(key) 先在注册表中按 key 查找,命中返回已有符号,未命中则创建新符号并登记;Symbol.keyFor(sym) 反向查询:符号若在注册表中则返回其 key,否则返回 undefined(Symbol() 创建的符号不在注册表,keyFor 返回 undefined)。注册表跨 Realm 共享:同源 iframe、Worker 之间 Symbol.for 同一 key 得到同一符号,可作为跨上下文协议标识(如共享 Symbol.iterator 约定、跨页面消息携带统一 symbol)。风险与边界:注册表是全局单例,滥用会让符号"不再唯一"(同 key 同符号,破坏唯一性初衷),且 key 字符串冲突会意外共享;符号入注册表后无法删除,长期运行会累积。工程上:协议级共享用 Symbol.for(需文档化 key 命名空间,如 "libname.field"),本地唯一性用 Symbol()。

本题考察注册表的机制与边界。核心是"按 key 复用 + 跨 Realm 共享 + keyFor 反向查询",滥用风险(唯一性丧失、累积)是深度考点;能给出 key 命名空间规范即展示工程实践。

#
★★

28. Proxy 与 defineProperty/Object.freeze 的边界,Proxy 无法拦截配置或扩展性内部槽的限制

Proxy 无法拦截哪些"配置或扩展性"内部槽操作?边界在哪里?

  • [[IsExtensible]]/[[PreventExtensions]] 的 trap 存在但受不变量约束
  • 不可扩展目标的虚拟键限制
  • defineProperty 与 isExtensible 的联动

Proxy 有 isExtensible 与 preventExtensions 两个 trap 分别拦截 Object.isExtensible 与 Object.preventExtensions,但它们受强不变量约束:preventExtensions trap 若返回 true,目标必须是真实不可扩展的(引擎会检查);isExtensible trap 的返回值必须与目标实际扩展性一致(代理不能谎报)。因此"代理无法凭空改变目标的扩展性事实":不可扩展(或已冻结)的目标上,ownKeys 不能返回额外虚拟键、getOwnPropertyDescriptor 不能声明新属性、set/defineProperty 不能新增属性,否则抛 TypeError。defineProperty trap 拦截 Object.defineProperty,但不可配置属性的描述符变更被引擎强制拒绝。边界总结:代理能拦截"操作的调用"(拦截点存在),但最终结果必须与目标内部槽事实相容——"操作拦截"不等于"事实篡改",这是代理安全性的根基(防篡改语义)。工程上:需要"只读视图"时应基于可扩展、可写目标做拦截,而不是冻结目标上强行代理。

本题考察代理的不可篡改边界。核心是"trap 存在但不变量限制事实一致性",扩展性/配置性相关 trap 是重点;能说明"操作可拦截、事实不可歪曲"的安全原则即展示规范级理解。

#
★★

29. Iterator Helper 方法(map、filter、take、drop,Stage 4 提案)在大型数据流的现代工程价值

Iterator Helper 方法(map/filter/take/drop)在大型数据流上有何现代工程价值?

  • 惰性求值避免中间数组
  • 无限/超大序列的流式处理
  • 与数组方法的使用边界

Iterator Helpers(ES2025,Stage 4)为迭代器对象(含生成器、数组迭代器、自定义迭代器)提供 map、filter、take、drop、reduce、toArray 等链式方法:与数组方法不同,它们惰性求值——链式调用只构建"管道"不立即执行,遍历时才逐个元素经过各转换,内存占用 O(1),不会为每个中间步骤创建数组。现代工程价值:处理无限序列(take(100) 截取无限生成器)、超大数据流(文件行、数据库游标、事件流)时的流式变换、避免 map→filter→map 链的多次数组分配(大数据集内存可差一个数量级);配合生成器天然组合。边界:惰性意味着"副作用在遍历时发生"(调试注意时机)、需要最终物化结果时显式 toArray/消费;对小型数组直接数组方法更简单直观(函数签名一致但语义不同);共享迭代器只能消费一次,重复遍历需重新创建。

本题考察惰性迭代方法的工程定位。核心是"惰性管道 + O(1) 内存 + 无限序列"三价值与"消费一次、副作用时机"两边界;能对比数组方法给出选型即展示实用判断。

#
★★

30. 元编程(Reflection / Decorators)

JS 的元编程能力(Reflection 与 Decorators)是如何构成的?

  • 反射类 API(Reflect/Proxy)的运行时能力
  • 装饰器与元数据的设计时能力
  • 元编程的应用边界

JS 元编程分两个层次:运行时反射——Reflect 提供内部方法的标准函数化入口(apply、get、set、ownKeys 等),Proxy 提供对对象操作的全面拦截(读写、枚举、删除、调用、构造),二者组合实现运行时"检查与修改程序自身结构"(动态代理、AOP、响应式);设计时元编程——装饰器(Stage 3)以 (value, context) 声明式地包装类成员,配合 Symbol.metadata 记录元数据,在定义期注入行为(DI、序列化、路由),是"声明式修改程序结构"。应用边界:运行时反射适合动态数据与拦截型能力(响应式、校验、网关);设计时元编程适合框架级声明(组件标记、依赖注入);反射开销与可读性成本应在设计时评估——过度反射使代码难以静态分析与调试,元编程应集中于框架层而非业务层。现代方向:装饰器与元数据标准化后,反射编程正从"手写约定"走向"标准协议"。

本题考察元编程的体系构成。框架是"运行时(Reflect/Proxy)vs 设计时(Decorator/Metadata)"两个层次,应用边界落在框架层与业务层的分工;能给出每层的典型场景即展示系统性认知。

#

31. Proxy 在 Immer 的不可变数据实现中扮演的拦截与写时复制角色

Proxy 在 Immer 的不可变数据实现中扮演什么角色?写时复制(copy-on-write)如何工作?

  • draft 代理与写时复制
  • 修改路径的克隆与结构共享
  • 冻结输出与性能边界

Immer 的核心是 produce(baseState, draft => {...}):传入的 draft 是 baseState 的 Proxy 包装,用户在"看似可变"的 draft 上直接修改;Immer 在 set trap 中记录修改,首次修改某对象时对其进行浅克隆(写时复制),后续修改作用于克隆,未修改的部分保持引用共享(结构共享)。提交时只把"被修改过的路径"重新组合,产出全新的不可变状态,其余子树复用原引用——保证不可变性(可用 Object.is 比较、自由 memo 化)且高效。Proxy 的角色:拦截 set/delete 识别修改路径并触发克隆;还负责懒初始化(访问到的子树才生成 draft 代理)。边界:性能与代理粒度相关(深对象修改需沿路径克隆,浅层引用多则共享多);draft 不可逃逸(在异步回调中继续修改 draft 不安全,需产出新 produce);Proxy 开销在超大状态的高频修改下需评估,可用 enablePatches 做增量补丁。

本题考察不可变库的代理实现。核心是"draft 代理 + 修改路径写时复制 + 结构共享提交"的完整链路,draft 逃逸与性能边界是工程重点;能解释 Object.is 比较收益即展示不可变数据价值认知。

#

32. Object.fromEntries 与 Proxy 拦截的 set trap 在 Map-like API 与迭代协作的工程价值

Object.fromEntries 与 Proxy 的 set trap 如何在 Map-like API 与迭代协作中发挥作用?

  • fromEntries 的键值对反构造
  • set trap 校验与规范化
  • 迭代协议与枚举的一致性

Object.fromEntries(entries) 把 [key, value] 迭代序列构造为普通对象,是 Map→对象、entries 变换后的回构的便捷通道;与 Proxy 的 set trap 协作可构建"Map-like"对象 API:set trap 在写入时校验类型、规范化键(如 Symbol 键策略、日期字符串化)、触发回调(类似 Observable 的更新通知),get 从内部存储读取,deleteProperty 维护删除语义。迭代协作:为这类对象实现 [Symbol.iterator](返回 entries 迭代器)使其可被 for...of、展开与 fromEntries 消费,实现"对象↔Map"的互操作;注意 set trap 与迭代数据的口径一致(写入规范化的键,迭代时同样规范化返回),ownKeys/描述符与迭代键集合对齐,避免"对象键与迭代内容不一致"。工程价值:表单模型、配置中心、状态仓储的"可校验可迭代"统一接口。

本题考察对象式数据结构的组合构建。核心是"fromEntries 反构造 + set trap 校验写入 + Symbol.iterator 迭代互通"的三件套,一致性治理是工程重点;能给出表单/配置场景即展示应用价值。

#

33. Proxy 的 apply 与 construct trap 在函数与 class 实例化时的元编程边界

Proxy 的 apply 与 construct trap 在函数调用与 class 实例化时有哪些元编程边界?

  • apply 拦截函数调用
  • construct 拦截 new 与 newTarget
  • 函数代理的边界(原型、可调用性)

apply trap 拦截函数的调用(proxy(...args) 与 Reflect.apply),可在执行前改写参数、执行后改写返回值或附加行为(AOP:日志、缓存、鉴权、重试),实现函数级装饰;construct trap 拦截 new proxy(...args),接收 (target, args, newTarget),可改写构造流程(返回代理实例、注入初始化、控制 newTarget 决定实例原型来源),是"类级元编程"的入口(如 DI 容器用 construct 拦截实例化注入依赖)。边界:apply 只对可调用目标生效(目标必须是函数,否则 new 或调用抛 TypeError);construct 的返回值若非对象则引擎自动用 newTarget.prototype 创建实例(不能返回原始值);函数代理的 prototype 属性行为需谨慎(实例的原型链以 newTarget 为准);性能上函数代理无法被内联优化,热点路径慎用;apply/construct 与 Reflect.apply/construct 转发配合保持默认语义。

本题考察函数与构造的代理能力。核心是"apply 做调用级 AOP、construct 做实例化级控制",newTarget 语义与返回值边界是细节考点;能给出 DI 拦截示例即展示元编程应用能力。

#

34. Generator.throw() 在协程式错误传播与 co 风格异步流的工程实践

Generator.throw() 如何实现协程式错误传播?在 co 风格异步流中有何工程实践?

  • throw 注入错误到暂停点
  • 生成器内 try/catch 捕获与恢复
  • co 运行时错误传播的实现

iterator.throw(err) 把错误注入生成器当前的 yield 暂停点:如同该处抛出异常,生成器内可用 try/catch 捕获(捕获后可继续 yield,实现错误恢复),若未捕获则错误沿生成器向外传播(迭代器抛出,next 也返回 rejected 状态)。协程语义:生成器与外部是"双向通道"——next 传值、throw 传错,外部运行时可精确控制错误进入的位置,这在 co 风格异步运行时是核心机制:yield 一个 Promise 时,运行时 next(promise 结果);Promise 拒绝时调用 iterator.throw(reason),让错误在"yield 那一行"抛出,从而被生成器内 try/catch 捕获或沿调用栈冒泡——错误位置与同步代码一致,栈语义清晰。工程实践:redux-saga 的 take/call 等 effect 在错误时即用 throw 注入实现流程回退(try/finally 清理、catch 分支重试);手写 co 时注意 throw 返回的也是迭代器结果对象({value, done}),需继续推进迭代。

本题考察生成器错误通道的机制。核心是"throw 注入暂停点 + 内部可捕获恢复 + 未捕获向外传播"的协程语义,co 运行时的错误传播映射是价值落点;能说明 saga 用 throw 实现回退即展示实践认知。