通信与沙箱隔离

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

1. Storage Access API 在 SSO/IdP 的工程价值

Storage Access API 是什么?它在 SSO/IdP(身份提供方)场景下有什么工程价值?

  • Storage Access API 的机制:第三方上下文(iframe)中请求存储访问权
  • 与 SameSite/ITP 等浏览器策略的关系
  • 在 SSO/IdP 中的价值:跨站 iframe 登录态与 Cookie 访问

Storage Access API 允许嵌入的第三方上下文(如 iframe 中的 IdP 页面)在获得用户许可后访问自己的 Cookie 与存储:第三方 iframe 调用 document.requestStorageAccess(),浏览器按用户交互与许可状态决定是否授权,授权后 iframe 内可读取自己的 Cookie(含 SameSite=None 之外的策略限制场景)。背景是浏览器隐私策略演进(SameSite=Lax 默认、Safari ITP 智能跟踪防护)大幅限制第三方上下文的 Cookie 访问,传统「隐藏 iframe + Cookie 同步」的 SSO 方案在第三方上下文失效。工程价值:在 SSO/IdP 场景,主站以 iframe 嵌入 IdP 域做静默登录态检查与会话刷新时,Storage Access API 提供「用户显式许可后的受控访问」通道——首次交互时请求授权(用户点击登录按钮即满足交互要求),授权后 iframe 内可读写自身会话 Cookie,实现跨站登录态维持;配合 postMessage 与 OpenID Connect 的 prompt 流程,把「浏览器限制」变成「可编排的授权流程」。要点:请求必须在用户手势(点击)中发起才容易被接受;授权按站点对持久化(用户拒绝后要提供降级——跳转顶层导航登录);与 Fenced Frame、FedCM 等新能力的协同演进。

回答按「API 机制(第三方上下文受控访问)→ 浏览器隐私策略背景 → SSO 场景价值(会话检查、登录态维持、授权编排)→ 工程要点(手势触发、降级)」,核心是「把浏览器的存储限制转化为显式授权流程」。

#
★★★

2. Trusted Types 在子应用 DOM 注入的工程价值

Trusted Types 是什么?它在微前端子应用 DOM 注入场景下有什么工程价值?

  • Trusted Types 的机制:DOM XSS 注入点的类型约束
  • 在微前端中的价值:子应用动态 DOM 操作的统一收口
  • 与 CSP、sink 审计的协同

Trusted Types 是浏览器防御 DOM XSS 的机制:通过 CSP 的 require-trusted-types-for 'script' 策略,强制所有 DOM XSS sink(innerHTML、document.write、script src 等)只能接收 TrustedHTML/TrustedScript/TrustedScriptURL 类型对象,普通字符串直接抛错——把「注入点可控」从运行时检测变成「编译/静态可审计」。微前端场景的工程价值:子应用动态生成 DOM(模板渲染、富文本、iframe/script 注入)是 XSS 高发区,Trusted Types 让这类操作必须经过统一「策略工厂」(trustedTypes.createPolicy)收口——所有动态 HTML 必须经策略校验(过滤脚本、校验 URL 白名单)才能注入,绕过即报错;对第三方库(操作 innerHTML 的旧库)通过「默认策略」做审计与兼容处理,把「谁的代码在写 DOM」变成可枚举清单。与沙箱的关系:Trusted Types 是「浏览器层的注入约束」,沙箱是「JS 全局隔离」,二者互补——沙箱挡不住「沙箱内代码合法调用 innerHTML 注入脚本」,Trusted Types 正好补上这一层。落地:策略集中定义(白名单校验、转义、URL 协议过滤)、CSP 先 report-only 观察违规再 enforced、违规事件上报纳入 XSS 监控。

回答按「机制(sink 类型约束 + CSP 强制)→ 微前端价值(DOM 注入统一收口、第三方库审计)→ 与沙箱互补 → 落地(策略工厂、report-only 过渡)」组织,核心是「Trusted Types 把 XSS 防御从运行时不定期变为结构强制」。

#
★★★

3. qiankun 的 LegacySandbox(基于 Proxy 的单实例沙箱)与 ProxySandbox(多实例沙箱)的历史演进与适用场景

qiankun 的 LegacySandbox 与 ProxySandbox 有什么历史演进关系?各自的适用场景是什么?

  • LegacySandbox 的机制:单实例 Proxy 沙箱(记录-回滚)
  • ProxySandbox 的演进:多实例并存与激活切换
  • 演进动因与适用场景判断

LegacySandbox 是单实例 Proxy 沙箱:用 Proxy 拦截 window 读写,写入的属性记录到「变更集合」,子应用失活时按集合回滚(还原 window 原状)、激活时恢复;比 SnapshotSandbox 高效(只记录变更而非全量快照),但仍是单实例——同一时刻只允许一个子应用激活,后激活会与前者冲突,且失活回滚会清掉自己写入的全部全局。ProxySandbox 是面向多实例的演进:每个子应用拥有独立 proxy 与独立值表,激活时全局被替换为该应用 proxy,多应用可并存(各自值表互不干扰)、切换零回滚成本(不修改真实 window,只换代理指向)。演进动因:业务出现「多子应用同屏」(dashboard、工作台)与快速切换需求,单实例的「独占全局 + 回滚」模型不再适用;ProxySandbox 用「空间换时间」(每实例一套值表)换来了并存与切换性能。适用场景:LegacySandbox 适合「单实例 + 需要记录回滚语义 + 轻量」(历史项目、主从切换简单场景);ProxySandbox 适合「多实例并存 / 高频切换 / 强隔离」的现代场景,也是当前默认。注意:两者都依赖 Proxy,老浏览器需降级 SnapshotSandbox。

回答按「两机制(记录回滚 vs 独立值表)→ 演进动因(多实例与切换需求)→ 适用场景与兼容降级」组织,核心是「沙箱演进史就是『从独占回滚到分账并存』的演进」。

#
★★★

4. 微前端共享状态,Pinia/Redux 切片、SharedWorker、URL/Storage 在跨子应用的工程价值

微前端跨子应用的共享状态有哪些方案?Pinia/Redux 切片、SharedWorker、URL/Storage 各自的工程价值与边界是什么?

  • 共享状态的形态:运行时共享、跨进程共享、跨 Tab/会话共享
  • 各方案机制与适用场景
  • 边界:一致性、序列化、生命周期与安全

三类方案解决不同「共享维度」:Pinia/Redux 切片——把全局状态管理库的 store 切分(基座持有公共切片,子应用按域挂载自己的切片),解决「同一进程内跨应用共享响应式状态」:共享数据随应用共存亡、响应式更新即时、与 UI 绑定自然;边界是共享范围受限于「同页面同进程」、需要统一的 store 实例(反模式是各子应用各建 store 再强行同步)。SharedWorker——单例 worker 进程可被同源多页面/多应用共享,解决「跨页面/跨标签的共享状态与计算」:共享会话数据、缓存、轮询聚合,页面刷新不丢;边界是同源限制(跨域子应用需服务端中转)、序列化(结构克隆)、连接管理(端口与生命周期)。URL/Storage——把状态编码进 URL(查询参数、hash,可分享、可回退)或存 localStorage/sessionStorage(跨页面持久、刷新保留);边界是仅适合「可序列化、低频变更、非敏感」的轻状态(UI 偏好、过滤条件),不适合高频实时状态(无变更通知,需轮询或手动同步)。选型逻辑:同页面实时共享用 store 切片;跨 Tab 低频共享用 storage(配合 storage 事件);跨 Tab 高频/计算密集用 SharedWorker;可分享可回退的视图状态放 URL。安全与清理:Storage 键命名空间化、SharedWorker 与 store 的 teardown 与重连管理。

回答按「三类方案的共享维度(同进程/跨 Tab/可分享)→ 机制与边界 → 选型逻辑」组织,核心是「先定义共享维度,再选方案,没有万能方案」。

#
★★★

5. 主应用 -> 子应用 CustomEvent/postMessage/Props 三种通信方案的工程取舍

主应用向子应用传递数据时,CustomEvent、postMessage 与 Props 三种方案如何取舍?

  • 三种方案的机制:直接调用 vs 浏览器消息 vs 全局事件
  • 同步性、类型安全与耦合度的差异
  • 场景化选型与组合实践

Props 方案(qiankun 的 initGlobalState / 基座在 mount 时传参 + 回调):直接传递、同步可靠、类型安全(框架层面校验)、依赖显式(数据流清晰),适合「初始化参数与受控回调」——子应用依赖基座注入的能力(用户信息、跳转函数、请求实例);耦合度相对高(子应用与基座契约强绑定)。CustomEvent:子应用在 window/document 上派发自定义事件,基座监听(或反向),浏览器原生、异步、松耦合,适合「广播类消息」(刷新通知、语言切换、登出事件);代价是事件全局可见需命名空间治理、无类型校验、事件顺序无保证。postMessage:跨域/跨窗口通信的标准通道(iframe、window 引用),适合「跨域子应用」与「需要序列化传输」的场景;代价是异步(消息排队)、序列化限制(函数与 DOM 不可传)、需 Origin 校验。取舍:同域 + 强契约用 Props(直接、类型安全);同域 + 广播用 CustomEvent;跨域 + 结构化数据用 postMessage;工程组合——初始化用 Props、全局广播用 CustomEvent、跨域桥接用 postMessage,各按场景职责分工,避免「一种通道包打天下」的混乱。

回答按「三方案的机制与特性(同步/异步、类型、耦合)→ 场景映射 → 组合分工」组织,核心是「通道选择取决于耦合意图与跨域情况」。

#
★★★

6. 基于 CustomEvent、SharedWorker、BroadcastChannel 的通信方案

CustomEvent、SharedWorker 与 BroadcastChannel 三种通信方案各有什么特点?分别在什么场景使用?

  • 三种通道的机制:页面内事件、独立线程共享、同源广播
  • 时序、序列化与生命周期的差异
  • 场景选型:页面内/跨 Tab/跨进程

CustomEvent:在同一文档内通过 window/document 派发与监听,同页面各应用间通信,同步派发(监听器同步执行)、无需序列化(对象引用直传)、即时可靠;范围仅限当前页面(刷新即失)。BroadcastChannel:同源(同 origin)多上下文(多 Tab、同页 iframe、Worker)之间的广播通道,消息异步、结构化克隆序列化、自动按频道隔离(channel 名即命名空间);适合「同源跨 Tab 的轻量同步」(登录状态变化、缓存失效广播);限制是仅同源、无历史消息(新加入者收不到先前消息,需自己拉取状态)。SharedWorker:独立线程进程,多上下文共享连接(端口),能做「跨 Tab 的状态持有与计算聚合」(会话状态、请求合并、轮询集中);能力最强(可维护状态、可做复杂逻辑),但复杂度最高(连接管理、协议设计、异常处理),且同样受同源限制。选型:页面内通信用 CustomEvent;同源跨 Tab 广播用 BroadcastChannel(简单场景)或 SharedWorker(需要共享状态与计算时);跨域场景降级为 postMessage 或服务端通道。工程要点:三种通道的生命周期管理(页面销毁断开)、消息协议与命名空间统一、以及「跨域需求的早期识别」(避免同源方案做一半发现跨域)。

回答按「三通道机制对比(页面内/广播/共享线程)→ 序列化与生命周期差异 → 场景选型与降级」组织,核心是「通道选择 = 通信范围(页面/同源跨 Tab/跨域)× 状态需求(纯消息/共享状态)」。

#
★★★

7. 微前端路由 activeRule 与子应用生命周期对齐

微前端的路由 activeRule 如何设计?activeRule 触发与子应用生命周期如何对齐?

  • activeRule 的匹配机制:路径/函数/正则
  • 激活与生命周期的时序:匹配 → 加载 → 挂载 → 卸载
  • 边界情况:重叠规则、切换竞态与保活

activeRule 是基座判断「当前路由属于哪个子应用」的规则:支持路径前缀('/app1')、正则与函数(自定义匹配逻辑)。对齐设计:路由变化 → 基座按注册顺序评估 activeRule → 命中则触发子应用加载(首次)与挂载(复用),未命中则触发卸载;生命周期时序「先路由后挂载」——挂载时机要等「路由已归属 + 容器就绪」,卸载时机是「路由离开其管辖范围」。工程要点:规则不重叠——子应用路径前缀互斥,避免多规则同时命中(命中顺序按注册序,但依赖顺序是脆弱设计);切换竞态——快速路由变化时「旧应用卸载未完成、新应用挂载开始」,需要状态机串行化(切换队列)或代际校验;保活与路由——keep-alive 应用「路由离开不卸载(隐藏)」,激活规则命中即恢复显示,注意隐藏期间路由与状态的一致性;子应用内部路由——子应用内导航不离开其前缀,activeRule 稳定命中;跨应用跳转由基座路由接管。与生命周期契约联动:unmount 的触发语义「路由失效 + 无保活」要显式文档化,避免「以为卸载了其实还在跑」的坑。

回答按「activeRule 机制 → 激活与生命周期时序 → 边界(重叠、竞态、保活、内部路由)」组织,核心是「activeRule 是路由域与生命周期域的衔接点,对齐设计决定切换质量」。

#
★★★

8. iframe postMessage 的 Origin 校验与序列化边界

iframe 通信中 postMessage 的 Origin 校验如何做?序列化边界有哪些?

  • postMessage 的异步消息模型与 targetOrigin 参数
  • 接收端 Origin 校验的两种方式与安全意义
  • 结构化克隆的序列化边界:函数、DOM、循环引用

postMessage 的安全核心是「双向身份确认」:发送端 targetOrigin 参数指定消息只投递给期望的源(不写或 '*' 会把消息发给任意嵌入者);接收端必须校验 event.origin 是否为信任源(白名单判断,不能信任 event.source 之外的任何信息),否则恶意 iframe/窗口可伪造消息注入行为。序列化边界由结构化克隆算法决定:支持对象、数组、Date、Map/Set、ArrayBuffer、Blob 等,但不支持函数、DOM 节点、类实例原型(克隆后失去原型链,只保留数据结构)、Symbol、不可克隆对象(如部分平台对象);循环引用可克隆(变成共享引用图)。工程要点:消息协议版本化(字段加版本,避免新旧帧互解);对「类实例」消息用纯数据 DTO 传输(接收端重建对象);超大消息用分片或改走 SharedWorker/服务端(序列化与传输成本);对 iframe 通信做「一次握手」——建立连接时交换随机 token 与源校验,后续消息带 token 防伪。微前端场景:iframe 子应用与基座的所有通信统一走封装通道(内部完成 origin 白名单、序列化 DTO、协议版本管理),业务层不直接触碰 postMessage。

回答按「双向身份校验(targetOrigin + origin 白名单)→ 结构化克隆边界(支持/不支持)→ 工程要点(协议版本、DTO、握手)」组织,核心是「postMessage 的安全与正确性都靠显式约定,而非默认」。

#
★★★

9. Event Bus(mitt/EventEmitter3)在跨子应用事件的工程价值

Event Bus(mitt/EventEmitter3)在跨子应用事件通信中的工程价值是什么?使用边界在哪里?

  • Event Bus 的机制:发布订阅、同步派发、无依赖
  • 在微前端中的价值:轻量松耦合的广播通道
  • 边界:命名空间、生命周期、调试与状态

Event Bus(mitt、EventEmitter3)提供轻量发布订阅:on/off/emit 三件套、无运行时依赖、同步派发(emit 时监听器同步执行)、内存友好。在微前端的工程价值:作为「同页面跨应用的事件通道」比原生 CustomEvent 更可控(事件类型集中定义、类型提示、统一 off)、比全局状态库更轻(不引入状态管理语义,只做消息);适合广播类事件(登出、语言切换、主题变更、数据刷新通知),业务代码与「谁在听」解耦。使用边界与工程约束:命名空间——事件名按「应用域.事件」分层(app1.user.updated),集中登记在事件契约文档,避免冲突;生命周期——监听必须在组件/应用卸载时 off(mitt 不自动清理,泄漏的监听导致「幽灵事件」与内存泄漏);调试——事件总线是隐式调用,问题定位难,需要事件日志(发布/订阅登记、emit 记录);状态 vs 消息——总线只传「通知」,共享「状态」应交给 store(总线传状态增量容易不同步);性能——高频 emit 的同步派发会串行拖慢发送方(高频场景用节流或改 store)。工程实践:总线实例单例化(基座创建、子应用注入)、事件契约集中管理并做类型生成。

回答按「机制与价值(轻量松耦合广播)→ 边界(命名空间、清理、调试、状态/消息分工、性能)」组织,核心是「事件总线是消息通道不是状态层,治理重点在契约与生命周期」。

#
★★★

10. 前端消息总线的事件命名空间如何设计避免冲突,事件生命周期与清理怎么做?

前端消息总线的事件命名空间如何设计才能避免冲突?事件的生命周期与清理如何管理?

  • 命名空间的层级设计:域 + 模块 + 事件名
  • 事件生命周期:注册、触发、销毁的完整管理
  • 清理机制:自动清理、登记表与泄漏检测

命名空间设计的目标是「全局唯一、语义清晰、可归属」:采用层级命名「域.模块.动作」(如 app:user:updated、base:auth:logout),域按「应用或团队」划分(子应用各占一层),事件名用动作语义(updated/deleted/logged-out)而非模糊词(change);命名空间在「事件契约」中集中登记(唯一注册表),新事件先登记后使用,冲突在登记时发现;前缀避免保留字与框架内部事件冲突。生命周期管理:注册——统一入口(事件总线单例 + 类型化 API),业务层不允许裸字符串四处 emit;触发——emit 携带标准 payload(版本、来源标识),便于订阅方判断与过滤;销毁——两类清理:应用级(子应用 unmount 时统一 off 掉该应用注册的全部监听——靠「按域批量清理」能力,mitt 需自己维护域 → 监听器映射)、组件级(组件卸载时清理自身监听);泄漏检测——总线维护「活跃监听统计」,应用卸载后对比基线发现未清理监听并告警;对「一次性监听」(on 后只触发一次)提供 once 语义并确保执行后自动清理。

回答按「命名空间设计(层级 + 登记表)→ 生命周期管理(注册/触发/销毁的统一入口)→ 清理机制(按域批量 off、组件级清理、泄漏检测)」组织,核心是「总线治理 = 契约集中 + 清理闭环」。

#
★★★

11. Passkeys 在 SSO/IdP 的工程价值

Passkeys(通行密钥)是什么?它在 SSO/IdP 场景下有什么工程价值?

  • Passkeys 的机制:基于 WebAuthn 的公钥凭证、生物/设备解锁
  • 替代密码与降低钓鱼风险的安全价值
  • 在 SSO/IdP 的工程价值:跨站点认证、免密登录与恢复流程

Passkeys 是基于 WebAuthn/FIDO2 的公钥凭证体系:私钥保存在设备安全硬件(安全芯片/密钥管理器),公钥登记在服务端,认证时通过设备解锁(生物识别、PIN)完成签名挑战——密码不出设备、不传输,天然免疫钓鱼与撞库。安全价值:替代密码消除「弱密码、复用密码、密码泄露」风险,且与特定站点绑定(防钓鱼:凭证与 RP ID 绑定,假站点无法通过)。在 SSO/IdP 的工程价值:免密登录——IdP 登录页支持 Passkey 认证,用户一次生物验证完成登录;跨站点认证——Passkey 按域(RP ID)与 IdP 域绑定,SSO 生态中 IdP 域凭证可支撑其管理域下的应用(同一公司多应用共用 IdP 认证),配合 conditional UI(自动填充提示)提升无摩擦体验;防钓鱼 SSO——与传统的「密码 + MFA」相比,Passkey 认证过程不可被中间人转发(挑战与来源绑定)。工程要点:注册流程(用户先注册 Passkey,支持多设备与云同步)、恢复流程(设备丢失用其他凭证/恢复码,Passkey 的备份依赖平台同步)、降级(不支持 Passkey 的浏览器/环境回退密码 + MFA)、用户体验(生物验证失败的重试、跨设备认证的 QR 配对流程)。微前端/Web 侧:IdP iframe 场景下 Passkey 认证需要顶层上下文或允许的嵌入策略配合。

回答按「机制(公钥凭证 + 设备解锁)→ 安全价值(免疫钓鱼)→ SSO/IdP 价值(免密、跨应用、防钓鱼)→ 工程要点(注册/恢复/降级)」组织,核心是「Passkey 把认证从秘密共享变成签名证明」。

#
★★★

12. CORP/COEP 的跨域资源策略 与 DPoP(RFC 9449)

CORP(Cross-Origin-Resource-Policy)与 COEP(Cross-Origin-Embedder-Policy)是什么?DPoP(RFC 9449)在令牌安全中起什么作用?

  • CORP/COEP 的响应头/嵌入策略语义与安全价值
  • COEP 对资源加载的影响(需要 CORS/CORP 配合)
  • DPoP 的机制:证明令牌持有者身份、防重放与令牌盗用

CORP(Cross-Origin-Resource-Policy)是响应头(same-origin / same-site / cross-origin):资源方声明「谁可以嵌入我」,阻止跨站网页直接加载敏感资源(如 HTML 被第三方 iframe 引入),是「资源侧」的嵌入控制。COEP(Cross-Origin-Embedder-Policy)是文档级策略(require-corp / credentialless):页面声明「只嵌入明确允许的资源」——设置了 COEP: require-corp 的页面,所有跨源资源必须带 CORP/CORS 允许头,否则加载被阻断;它把「默认信任跨源资源」变成「默认拒绝、显式允许」,显著降低跨源注入风险,同时也是启用 SharedArrayBuffer 等隔离能力的前提(需要跨源隔离)。工程价值与代价:COEP 会破坏未配 CORS 的第三方资源(图片、字体、脚本)——迁移需资源侧补头或用 credentialless 模式折中。DPoP(RFC 9449,Demonstrating Proof of Possession):对访问令牌附加「持有证明」——客户端生成一次性 DPoP 密钥对,每次请求用私钥对请求的摘要(方法 + URL + 时间戳 + nonce)签名,把签名放入 DPoP 头;授权服务器/资源服务器验证签名与公钥绑定,使令牌与「持有者密钥」绑定——即使访问令牌泄露,攻击者没有对应私钥也无法使用(防令牌盗用);同时时间戳 + nonce 防重放。价值场景:公共客户端(SPA、原生应用)的令牌安全,比单独依赖令牌自身更抗泄露。

回答按「CORP(资源嵌入声明)→ COEP(页面嵌入策略与 CORS 影响)→ DPoP(令牌持有证明机制)」,核心是「CORP/COEP 管资源边界、DPoP 管令牌归属,都是纵深防御」。

#
★★

13. 全局状态污染与 tear-down 不彻底导致的内存泄漏应如何在工程上预防

全局状态污染与 tear-down 不彻底导致的内存泄漏如何在工程上预防?

  • 泄漏与污染的来源:全局变量、监听、定时器、缓存引用
  • 工程预防:生命周期钩子、登记表、依赖注入与静态检查
  • 检测与治理:泄漏监控、快照对比与审计

来源识别:全局状态污染(子应用写入未清理的全局变量、修改共享原型与全局配置)与 tear-down 不彻底(监听未移除、定时器未清、缓存引用未释放、组件闭包持有大对象)是两类主要泄漏。预防的工程手段:生命周期强制清理——unmount 钩子中按清单清理(框架组件卸载 + 全局副作用登记表双轨,清单随应用维护);登记表机制——所有「需要在卸载时清理的东西」(监听、定时器、interval、observer、worker)统一登记,unmount 时批量销毁(沙箱已内置该机制,业务侧同样模式);依赖注入替代全局访问——把「会泄漏的全局对象」(请求实例、store、事件通道)以注入方式交给子应用,卸载时统一归还/释放,避免业务代码自己持有全局引用(持有即泄漏);静态约束——lint 规则禁止直接 window.xxx 赋值、禁止模块级大对象缓存、组件卸载清理规则;快照检测——卸载前后对比「监听数量、定时器句柄数、全局键集合」,差异超阈值即告警(把泄漏变成可测的指标);内存监控——Performance.memory(非标准)/ 堆快照对比识别「反复切换后内存不回落的子应用」。治理闭环:泄漏告警归因到应用与版本,修复后回填基线;把「重复挂载卸载内存平稳」做成 CI 用例(多轮切换后堆增长 < 阈值)。

回答按「来源(污染 + tear-down 不彻底)→ 预防(清理清单、登记表、注入替代全局)→ 检测(快照对比、内存监控、CI 用例)」组织,核心是「预防靠机制(登记表 + 注入)、验证靠指标(快照与堆)」。

#
★★

14. qiankun 的 globalState 突变通知在大型应用中引发的过度 re-render 应如何用选择器剪裁

qiankun 的 globalState 突变通知为什么引发过度 re-render?如何用选择器剪裁?

  • globalState 的广播机制:全量通知 vs 定向通知
  • 过度 re-render 的成因:订阅粒度粗、无浅比较
  • 剪裁方案:选择器订阅、版本号、局部状态化

成因:qiankun 的 onGlobalStateChange 把「全局状态变更」广播给所有订阅者(子应用 + 基座),回调里若直接 setState 或触发渲染,任何字段变化都会引发所有订阅方全量重渲染——状态字段多、订阅方多、回调无细粒度过滤时,一次无关紧要的字段更新(如主题色)就重渲整个页面。剪裁方案:选择器订阅——订阅回调里只「提取自己关心的字段」并与上次比较(浅比较/引用比较),字段未变则不再触发渲染(把「全局状态变化」过滤成「我的字段变化」);库层面用状态选择器(类似 Redux 的 useSelector / Vue 的 storeToRefs + 响应式)把「订阅整个 store」变成「订阅切片」;分片状态——把大 globalState 按域拆成多个小状态(auth、theme、notifications 各自独立),订阅粒度天然变细;版本号机制——变更时只广播「变更的字段路径 + 版本号」,订阅方按路径匹配,未命中路径不处理;最后兜底:把「高频变化、仅局部需要」的状态移出 globalState(放本地 store),只有「低频、跨应用必需」的状态才进全局通道。工程要点:订阅回调幂等(重复触发无害)、选择器返回稳定引用(避免每次新对象导致渲染循环)。

回答按「成因(全量广播 + 粗粒度订阅)→ 剪裁(选择器、分片、路径版本号)→ 兜底(高频状态本地化)」组织,核心是「全局状态的收益在共享,代价在耦合,用订阅剪裁换性能」。

#
★★

15. postMessage 在跨域子应用通信中的 message event 监听冲突与命名空间约定

跨域子应用通信中 postMessage 的 message 监听有哪些冲突?命名空间约定如何设计?

  • message 事件的全局性:所有窗口消息汇聚到同一事件流
  • 冲突形态:同类型消息互串、格式歧义、恶意伪造
  • 命名空间与协议约定:消息类型、版本、来源标识

message 事件的全局性导致冲突:页面所有 window(含 iframe、跨域子应用、广告位)的 postMessage 都汇聚到同一 message 事件流,监听方如果不做区分,会收到「不是发给自己的消息」——同类消息互串(两个子应用都用 {type:'refresh'})、格式歧义(消息结构相似无法区分)、以及恶意窗口伪造消息注入行为。治理的核心是「消息协议化」:命名空间——消息体统一带 type 字段且按「域.动作」命名(app1:user:updated),类型集中登记避免同名歧义;来源校验——监听时按 event.origin 白名单 + event.source 身份(握手时登记信任窗口引用)双重过滤,非信任来源直接丢弃;协议版本——消息体带 version 字段,接收方按版本解析(新旧并存期兼容);目标标识——消息体声明「期望的接收方」(targetId),广播类消息也要带来源标识(from)让接收方可过滤。工程落地:跨域通信统一走「消息通道封装」(内部完成 origin 校验、类型登记、版本处理),业务层不直接 addEventListener('message');监听器的注册与注销纳入生命周期管理(应用切换时移除,避免陈旧监听串扰)。

回答按「冲突成因(全局事件流 + 无区分)→ 协议化治理(type 命名空间、origin/source 校验、版本)→ 封装落地」组织,核心是「postMessage 的正确性全靠显式协议,松散使用必然冲突」。

#
★★

16. SharedWorker 作为跨子应用状态共享层的工程边界与回退方案

SharedWorker 作为跨子应用状态共享层有哪些工程边界?不可用时的回退方案是什么?

  • SharedWorker 的能力:跨 Tab 单例、共享状态与计算
  • 工程边界:同源限制、连接生命周期、调试与可靠性
  • 回退方案:BroadcastChannel、storage 事件、服务端中转

SharedWorker 的价值:跨同源 Tab/页面共享单例 worker(状态、计算、连接),子应用与基座可共享「会话状态、实时数据缓存、聚合轮询」,页面刷新不丢。工程边界:同源限制——不同域的子应用无法直接共享(跨域需服务端中转);连接生命周期——每个页面建立端口连接,页面关闭要断开,worker 的活跃判定(所有端口关闭后可能被回收)影响状态持久性假设;可靠性——worker 崩溃后状态丢失且无自动通知(需要心跳与重连机制);调试困难——worker 上下文调试工具支持弱于主线程;安全——共享状态可被同源任何页面读写,敏感数据不宜放。回退方案:BroadcastChannel——同源广播替代「只读同步」场景(状态变更广播 + 各自本地缓存),实现简单但无状态持有;storage 事件——localStorage 写入触发其他 Tab 的 storage 事件,可做「简单状态同步」但粒度粗(整个键变更)、无计算能力;服务端中转(WebSocket/SSE)——跨域与「需要权威状态」场景,页面订阅服务端推送,一致性最强但引入网络依赖。选型逻辑:跨域或需要权威一致性用服务端;同源轻量广播用 BroadcastChannel;需要「同源 + 状态持有 + 计算聚合」才用 SharedWorker,并配套「不可用降级」(feature 检测失败自动切 BroadcastChannel + 本地缓存)。

回答按「能力与边界(同源、生命周期、可靠性、调试)→ 回退梯度(BroadcastChannel / storage / 服务端)→ 选型与降级」组织,核心是「共享层按『状态持有需求 × 跨域需求』选择,且必须有降级路径」。

#
★★

17. CustomEvent 与 EventTarget 在子应用边界通信中的实际价值

CustomEvent 与 EventTarget 在子应用边界通信中的实际价值是什么?如何使用?

  • CustomEvent 的机制:自定义事件类型与 detail 数据
  • EventTarget 作为事件中心的用法
  • 边界通信中的价值:浏览器原生、解耦、可组合

CustomEvent 允许业务定义自己的事件类型(new CustomEvent('app1:refresh', { detail: {...} })),detail 承载任意数据,通过目标对象的 dispatchEvent 派发;EventTarget 是所有事件目标的基类,可以实例化独立的事件中心(new EventTarget())作为「应用内/跨应用的事件枢纽」,把监听器与 DOM 解耦。在子应用边界通信的价值:浏览器原生通道(无第三方依赖、无序列化成本——detail 是对象引用直传,仅限同文档)、语义清晰(事件类型即协议)、与 DOM 事件体系统一(可组合监听、支持 once/冒泡选项);相比自定义事件总线,CustomEvent 是平台标准(可被 DevTools 观察、可被测试工具模拟)、相比全局 store 更轻量(只传消息不传状态)。使用要点:事件类型全局登记避免冲突(域名.动作命名);监听器绑定到「事件中心实例」(注入的 EventTarget)而非裸 window(作用域隔离、便于整体清理);detail 承载纯数据;派发与监听的时序(派发早于监听会丢事件——需要时用「状态 + 事件」组合,先读状态再订阅变化);子应用边界用「共享事件中心」注入(基座创建、子应用接收),unmount 时整体 off。

回答按「机制(CustomEvent + EventTarget 中心)→ 价值(原生、解耦、可组合)→ 使用要点(命名、注入中心、时序与清理)」组织,核心是「CustomEvent 的价值在『用平台机制做应用协议』」。

#
★★

18. BroadcastChannel 在同源多 Tab 状态同步中的事件可靠性与去重策略

BroadcastChannel 在同源多 Tab 状态同步中如何保证事件可靠性与去重?

  • BroadcastChannel 的投递语义:尽力而为、无确认
  • 可靠性问题:消息丢失、无历史消息、并发竞态
  • 去重策略:幂等消费、版本号与协调者模式

BroadcastChannel 的投递是「尽力而为」:同源多上下文(Tab/Worker)广播,但无投递确认——消息可能丢失(目标未就绪、异常)、没有历史消息(后加入的 Tab 收不到先前的状态,需主动拉取)、无顺序保证(并发广播的到达顺序不定)。可靠性治理:以「状态同步」而非「事件投递」为模型——不依赖每条消息必达,而是「广播方声明状态版本(version),接收方收到后比对版本、不一致则主动拉取全量状态」,把「消息可靠性」转化为「状态收敛」;权威源——单一写入方(如某个 Tab 持有权威状态,其他 Tab 只读),写入方广播版本变更,读方按版本拉取,避免多写并发竞态;持久化兜底——状态同时写 localStorage(本地持久),BroadcastChannel 只做「变更提示」,新 Tab/漏消息场景读 localStorage 恢复;重连拉取——Tab 激活(visibilitychange)与频道 join 时主动拉取最新状态。去重策略:消费幂等——状态更新操作按「版本号」判断(旧版本更新丢弃),使重复/乱序广播不影响结果;操作去重——对「基于事件的副作用」(如通知提示)用事件 ID + 已处理集合去重;节流合并——高频广播在接收侧合并(同版本多消息只处理一次),发送侧节流(短时间内合并为最新状态广播)。

回答按「可靠性问题(尽力而为、无历史、无顺序)→ 状态收敛模型(版本 + 主动拉取 + 权威源 + 持久化兜底)→ 去重(幂等消费、事件 ID、节流)」组织,核心是「把事件可靠性问题转化成状态收敛问题」。

#
★★

19. garfish 的 router-based 子应用切换时全局副作用清理与 unmount 钩子设计

garfish 的 router-based 子应用切换如何设计?全局副作用清理与 unmount 钩子有哪些要点?

  • garfish 的基于路由的子应用激活机制
  • unmount 钩子的职责与清理清单
  • 切换时序:卸载与挂载的编排、保活与销毁策略

garfish 采用 router-based 模型:路由匹配(basename 前缀)驱动子应用激活,进入前缀域时挂载、离开时卸载,与 qiankun 的 registerMicroApps 同类但更强调路由域与子应用域的一一映射。unmount 钩子设计的要点:幂等——反复调用不报错、不重复清理;全量清理——DOM 容器清空、全局监听移除(window/document)、定时器与动画帧取消、请求中止(AbortController)、全局变量与样式还原(由沙箱协同)、store 状态回滚;异步完成——unmount 返回 Promise,基座等待完成才释放资源(避免「旧的还在跑、新的已挂载」);清理失败上报——unmount 抛错时进入降级路径(强制清空容器 + 告警),不让切换卡死。切换编排:串行化——「旧卸载 → 新挂载」串行执行(或「新加载并行、切换原子」),防止竞态(快速连点路由);保活策略——keep-alive 子应用切走不执行真实 unmount(隐藏 + 暂停定时器与动画),返回时恢复,注意「保活 ≠ 不清理」(隐藏期间的大定时器仍要管理);销毁 vs 保活按「切换频率 × 重建成本」决策,destroy 模式执行完整 unmount。工程上把「清理清单」做成框架约定(子应用模板内置),配合「卸载后快照检测」(监听数、全局键)验证清理彻底。

回答按「router-based 激活模型 → unmount 契约(幂等、全量、异步、失败降级)→ 切换编排(串行化、保活与销毁)」组织,核心是「切换质量 = unmount 契约 × 编排时序」。

#
★★

20. Module Federation 在 Vite/Rspack 下的运行时共享与版本冲突解决

Module Federation 在 Vite/Rspack 构建下如何实现运行时共享?版本冲突如何解决?

  • Vite/Rspack 下 MF 的接入方式(@module-federation/vite、Rspack 原生插件)
  • 运行时共享的语义一致性:shared 协商、singleton
  • 版本冲突的检测与治理:requiredVersion、strictVersion、契约测试

接入方式:Vite 生态用 @module-federation/vite(或 enhanced 包)把联邦语义映射到 Vite 构建(dev 模式以中间件/插件实现运行时联邦,build 产出 remoteEntry 与 chunk);Rspack 提供与 webpack 对齐的 ModuleFederationPlugin(配置项基本兼容),性能更高。运行时共享的语义在两个构建器下要保持一致:shared 声明 → 运行时 share scope 注册 → 版本协商(requiredVersion 匹配则复用、否则自载)、singleton 强制单例——Vite 的 ESM 产物与 webpack 的运行时格式不同,联邦运行时负责跨构建器的兼容(2.0 的独立 runtime 使不同构建器产物可互操作)。版本冲突的解决:协商优先——合理设置 requiredVersion(semver 范围)让版本重叠的依赖复用;检测兜底——strictVersion 把「singleton 版本不匹配」从告警升级为报错,CI 契约测试(组合宿主 × 远程 × 版本矩阵)在发布前发现冲突组合;治理——统一依赖版本策略(框架版本升级窗口)、snapshot 锁定发布时的依赖状态、冲突告警进监控。注意点:Vite dev 模式与 build 模式的联邦行为差异(dev 用原生 ESM + 插件处理,需单独验证)、Rspack 对 MF 2.0 实验特性(runtimePlugins、snapshot 全量)的支持进度、双构建器并存时的共享依赖格式兼容。

回答按「接入方式 → 运行时共享语义的一致性 → 版本冲突的协商/检测/治理闭环」组织,核心是「构建器变了,共享契约不变,冲突治理靠协商 + 契约测试」。

#
★★

21. 子应用间 CSS 变量(Custom Properties)

子应用间的 CSS 变量(Custom Properties)如何管理?如何避免互相污染并实现主题协作?

  • CSS 变量的继承与作用域机制(自定义属性沿 DOM 树继承)
  • 污染场景:全局声明覆盖、同名变量冲突
  • 治理:容器根声明、命名空间与主题令牌协作

CSS 变量(Custom Properties)沿 DOM 树继承:变量定义在某个元素上,其后代均可读取;documentElement(:root)上声明的变量全局可见——这正是污染与协作的根源。污染场景:子应用在 :root 声明同名变量(--primary-color)覆盖其他应用的样式取值;全局 reset/主题脚本改写 :root 变量影响所有应用。治理方案:容器根声明——子应用的变量声明在自己容器根(如 [data-app="orders"] 或 shadowRoot)而非 :root,随容器生命周期生效与回收,天然作用域化;命名空间——变量名带应用/域前缀(--app1-primary、--ds-button-bg),避免同名覆盖;主题协作——跨应用的主题变量(设计 token)由基座在 :root 统一声明并治理(只允许声明在共享令牌清单内的变量),子应用只消费不改写;继承设计——把「可被继承的全局影响」(字体、主题色)与「应用私有变量」分层,全局层走令牌清单、私有层走容器根。工程要点:动态主题(运行时切换)通过「给根元素换 data-theme 属性 + 变量集切换」实现,避免直接改写 :root 造成全局副作用;shadow DOM 内 CSS 变量仍可继承外部(变量穿透 shadow 边界,样式规则不穿透),可借此做「影子主题」;对旧代码(直接在 :root 声明)用运行时扫描与告警治理。

回答按「变量继承机制 → 污染场景 → 容器根 + 命名空间 + 令牌清单的治理 → 动态主题」组织,核心是「CSS 变量按需下沉:共享令牌归基座、应用变量归容器」。

#
★★

22. Pinia/Redux 在子应用内独立 store 的隔离策略与全局状态被反模式拉回的防护

子应用内独立的 Pinia/Redux store 如何隔离?如何防止「全局状态被反模式拉回」?

  • 子应用独立 store 的隔离模型:实例隔离与命名空间
  • 反模式拉回的形态:直接引用基座 store、跨应用共享同一实例
  • 防护:依赖注入边界、契约通道与代码评审约束

隔离策略:每个子应用持有自己的 store 实例(Redux 独立 createStore、Pinia 独立 pinia 实例),状态、reducer/action 互不可见;对「确实要共享的少量状态」走显式通道(globalState/事件/共享 store 切片)而不是把整个 store 暴露出去。反模式拉回的形态:子应用直接 import 基座的 store 模块(绕过边界共享内存状态)、跨应用使用同一 store 实例(修改全局状态互相污染)、把「共享 store 当作万能通道」导致状态演进不受控。防护手段:依赖注入边界——子应用运行时只接收「注入的共享能力」(共享 store 的受控接口:getState/subscribe/dispatch 的封装),拿不到 store 模块本体,编译期也无法直接 import(依赖边界由包结构与 lint 强制:禁止跨应用 import store 源码);契约通道——共享状态用「消息/事件 + 状态快照」而不是共享引用(订阅共享事件 + 本地状态副本,消费方解耦);评审与静态检查——lint/依赖图检查禁止子应用 import 基座内部模块、禁止全局 store 单例被多应用改写;状态治理——共享状态收敛为「低频、小、可序列化」,高频复杂状态留在应用内,防止共享层膨胀。

回答按「隔离模型(独立实例)→ 反模式形态(直接引用、共享实例)→ 防护(注入边界、契约通道、静态检查)」组织,核心是「隔离靠边界,拉回靠通道受控」。

#
★★

23. window/document 代理的边界场景

window/document 代理有哪些边界场景?哪些访问无法被代理覆盖?

  • 代理覆盖的语义盲区:Symbol、不可配置属性、跨 realm 引用
  • 访问形态的边界:函数内部引用、缓存引用、iframe 访问
  • 边界场景的工程处理:兜底隔离、代码约束与适配

边界场景列举:不可配置属性——window.window、window.self、window.top 等属性不可代理(代理对它们返回真实对象),子应用通过它们可拿到真实 window 逃出沙箱;Symbol 属性——Proxy 的 get 陷阱默认不拦截 Symbol 键访问(除非显式实现 Symbol 相关陷阱),Symbol.for 全局注册表跨沙箱共享;跨 realm 引用——通过 iframe.contentWindow、window.frames 拿到真实全局对象;缓存引用——第三方库初始化时 const win = window 缓存真实对象,后续绕过代理;document 的相似问题——document 对象上某些属性(如 document.defaultView 返回真实 window)与「document 对象本身被缓存」;函数化访问——通过构造函数(new Function)、getters 链(window.proto 等)间接获取未代理对象。工程处理:分层兜底——对「必须真实对象的场景」不做无谓代理(代理白名单模式:只代理需要的键),对「逃逸高危键」(top、parent、frames、defaultView)显式拦截或替换为受限对象;适配层——第三方库缓存引用的场景用「注入代理实例替代真实对象」或要求库改访问方式;强隔离兜底——高安全需求直接 iframe/ShadowRealm,不依赖代理的覆盖完整性;审计——运行时记录「未命中代理的访问」(get 陷阱未覆盖的键)纳入逃逸监控。关键认知:Proxy 沙箱的隔离承诺是「尽力而为的兼容层」,不是安全边界。

回答按「边界场景枚举(不可配置、Symbol、跨 realm、缓存、document)→ 处理策略(白名单代理、拦截、适配、强隔离兜底)→ 安全定位」组织,核心是「代理有语义盲区,工程上要按威胁模型分层兜底」。

#
★★

24. Shadow DOM 在样式与 DOM 隔离的工程应用

Shadow DOM 在微前端的样式与 DOM 隔离中有哪些工程应用?使用要点与限制是什么?

  • Shadow DOM 的封装语义:样式边界、DOM 封装、事件重定向
  • 微前端中的应用:子应用容器、组件级封装
  • 限制与适配:穿透、表单、焦点、全局继承

Shadow DOM 提供浏览器级的封装边界:shadow 树内样式不外泄(样式隔离)、外部样式不渗透(除继承属性与 CSS 变量)、shadow 内 DOM 对外不可见(getElementById/querySelector 隔离)、事件从 shadow 内部冒泡到外部时重定向 target(保护内部结构)。微前端应用:子应用容器挂 shadowRoot——qiankun strictStyleIsolation、micro-app shadow 模式即此思路,隔离最强(样式与 DOM 查询天然隔离);组件级——业务组件用 shadow 封装实现「真组件」(样式自包含、可被任意环境嵌入)。使用要点:样式穿透——需要外部定制时用 CSS 变量(穿透 shadow)与 ::part/::slotted(显式暴露接口),避免「无法定制」成为 shadow 的反对理由;表单与焦点——shadow 内表单控件与外部表单的关联(FormData 关联、autofocus)需适配;全局继承——字体、color 等继承属性与 CSS 变量仍从宿主流入(可用作主题通道);第三方库适配——依赖全局样式(antd reset、body 样式)与 document 查询的库在 shadow 内失效,需注入或改造;性能与调试——shadow 树创建与 DevTools 展开有少量成本,现代浏览器已优化。工程判断:shadow 是「强边界」方案,适合第三方内容与强隔离场景,业务应用内部按「边界需求」分级使用,避免「全量 shadow」带来的适配爆炸。

回答按「封装语义(样式/DOM/事件)→ 微前端应用(容器与组件)→ 穿透与适配要点 → 分级使用」组织,核心是「shadow 给强边界,代价是适配,按场景分级采用」。

#
★★

25. Realm API(提案)在 JavaScript 隔离的工程价值

Realm API(提案)在 JavaScript 隔离中的工程价值是什么?与现有沙箱方案的关系如何?

  • Realm 的隔离语义:独立全局对象与内置对象
  • 与 iframe、Proxy 沙箱的对比定位
  • 提案成熟度与工程落地预期

Realm API 提案提供「创建独立 JS 执行上下文」的能力:每个 Realm 拥有独立的全局对象、内置对象(Array、Object、Promise 等各自一份)与原型链,代码在不同 Realm 间通过值传递交互(对象需包装或克隆),从语言层面实现隔离——不依赖 DOM(比 iframe 轻)、不依赖代理拦截(比 with/Proxy 沙箱完整),原生语义上消除原型链污染、Symbol.for 跨域共享等 Proxy 沙箱的逃逸面。工程价值:微前端多实例隔离的理想底层——多个子应用各占一个 Realm,全局互不可见、内置对象不可互相污染,隔离强度接近 iframe 而无 iframe 的呈现与通信负担;适合「代码需要独立全局 + 模块化传入」的新场景。与现有方案的关系:Realm 是「语言级」隔离,Proxy 沙箱是「应用层」兼容方案(对存量代码兼容好但语义有盲区)、iframe 是「平台级」隔离(重量但彻底)——三者按「兼容性需求 × 隔离强度需求」分层;ShadowRealm 是 Realm 提案的落地形态(TC39 官方仓库现标注 Stage 2.7)。限制:提案仍处演进(标准 API 变化)、跨 Realm 对象传递需显式设计(依赖 DOM 的代码需桥接层)、生态工具(调试、source map)对 Realm 的支持待成熟——工程上适合「新架构 + 强隔离」场景先行验证,存量系统仍以现有沙箱为主。

回答按「Realm 隔离语义 → 与 iframe/Proxy 的定位对比 → 价值场景 → 成熟度与落地节奏」组织,核心是「Realm 是语言层隔离的补位,补 Proxy 的盲区、比 iframe 轻」。

#
★★

26. ShadowRealm(TC39 提案,现 Stage 2.7)在多实例加载的工程应用

ShadowRealm(TC39 提案,现 Stage 2.7)在多实例加载场景下如何工程应用?有哪些限制与设计要点?

  • ShadowRealm 的 API 语义:createRealm、importValue、值传递
  • 多实例加载:每实例一 Realm 的模型与共享设计
  • 限制:DOM 不可直达、全局同步、模块依赖解析

ShadowRealm 提供标准化的隔离 Realm 创建:realm = new ShadowRealm(),通过 importValue('module-url', 'exportName') 在 Realm 内加载模块并取回导出(导出值经「对象包装/复制」跨边界),Realm 内代码拥有独立全局,与外部通过值传递通信。多实例加载的工程模型:每个子应用实例一个 ShadowRealm——加载子应用模块到独立全局执行,多实例 = 多 Realm 互不干扰;共享设计——「共享能力」(框架、工具、事件通道)以「外部对象包装」传入(Realm 内拿到的是包装引用而非本体),或把共享模块在 Realm 内各自加载(重复但隔离,按需选择);DOM 不可直达——Realm 内代码不能直接操作宿主 DOM,需通过「桥接 API」:宿主把受限的 DOM 操作接口(渲染、事件绑定封装)作为值传入 Realm,子应用通过桥接层完成 UI——这是最大的工程改造点。设计要点:模块解析——Realm 内 import 的解析(相对路径、构建产物格式)需与构建体系对齐(打包成独立 bundle 或 ESM 产物);生命周期——Realm 的销毁与资源回收(Realm 无内建 destroy,靠丢弃引用 + GC,需显式清理 Realm 内注册的全局资源);同步限制——importValue 是异步的,加载编排需配合;兼容降级——不支持 ShadowRealm 的环境回退 iframe 或 Proxy 沙箱。定位:适合「模块化新代码 + 强隔离多实例」的现代架构,存量 DOM 直操代码改造成本高。

回答按「API 语义 → 多实例模型(一实例一 Realm + 桥接层)→ 限制与要点(DOM 桥接、模块解析、生命周期、降级)」组织,核心是「ShadowRealm 强隔离的前提是 DOM 访问桥接化」。

#
★★

27. Sandboxed iframes 与 allow-same-origin 的工程安全边界

Sandboxed iframes 的工程安全边界是什么?allow-same-origin 的组合使用有哪些风险?

  • sandbox 属性的限制语义与授权模型
  • allow-scripts + allow-same-origin 的组合风险
  • 微前端/嵌入场景的安全配置实践

sandbox 属性建立「默认拒绝」的边界:无脚本、无表单、无弹窗、无同源、无顶层导航,逐项授权(allow-scripts、allow-forms、allow-popups、allow-same-origin、allow-top-navigation 等)。工程安全边界的关键是「组合的语义反转」:allow-scripts + allow-same-origin 同开是危险组合——当 iframe 内容与父页面同源时,iframe 内脚本拥有同源权限(可读父页面存储与 DOM),且脚本可以「移除自身 sandbox 限制」(同源文档的脚本有权解除 sandbox 的某些限制),sandbox 形同虚设;因此「可执行脚本的同源 iframe」等于没有 sandbox 保护。安全配置实践:可执行脚本的 iframe 保持跨源(不给 allow-same-origin),让同源权限不成立;需要同源能力(localStorage、Cookie)时用 postMessage/受控桥接而非放开 allow-same-origin;微前端场景——iframe 承载子应用时按「信任分级」配置:第三方/低信任内容用「纯沙箱」(无同源、受限能力),自研子应用评估是否真的需要 allow-same-origin,需要时改用「无 sandbox + 应用层沙箱」并明示安全边界(应用层沙箱负责隔离);补充纵深:sandbox 不限制网络请求本身,配合 CSP(frame-src、connect-src)与出口管控。另一个组合风险:allow-top-navigation 允许 iframe 改父页面 URL,钓鱼与导航劫持面(按需授权)。

回答按「sandbox 授权模型 → allow-scripts + allow-same-origin 的组合反转风险 → 信任分级配置与纵深」组织,核心是「sandbox 的安全性取决于组合语义,不是授权关键字个数」。

#
★★

28. 微前端的子应用 JS 隔离(eval、new Function 拦截)

微前端的子应用 JS 隔离如何覆盖 eval 与 new Function?拦截的实现与边界是什么?

  • eval/Function 在沙箱中的执行上下文与逃逸风险
  • 拦截实现:代理构造器、包装与执行上下文注入
  • 拦截的边界:间接 eval、性能与兼容

隔离需求:eval('...') 与 new Function('...') 动态执行的代码不经过 with 包装的脚本作用域(eval 用调用处作用域、Function 构造全局作用域),直接访问真实全局,是沙箱逃逸与 XSS 注入的通道。拦截实现:替换全局入口——沙箱包装 window.eval(直接调用与 (0,eval) 间接调用的语义不同,需分别处理)与 Function 构造器,把「执行」改为「在沙箱上下文执行」:将代码字符串用 with(fakeWindow) 包装后再 eval(对 eval),或在构造出的函数体内注入沙箱全局访问层(对 Function,函数体在构造时拼接包装);更彻底的方案是「代码预处理」——在构建期/运行时把动态代码中的全局标识符改写为代理访问(依赖解析器,成本高)。执行上下文注入——除拦截入口外,确保动态代码内「声明的全局」(var/function 声明)落在沙箱值表而非真实全局。拦截边界:间接 eval(通过变量引用触发)与各平台内建调用路径可能绕过包装;Function 构造的代码经 eval 再间接执行路径复杂;字符串代码的静态分析(识别全局引用)成本高、有误判;拦截包装增加每次动态执行的解析开销(高频 eval 场景性能敏感)。工程结论:拦截是「尽力而为」——与 CSP 的 unsafe-eval 约束叠加(浏览器层封死执行入口)、配合代码审计(扫描动态代码使用)、对可信代码维持拦截、对不可信代码用 iframe/Realm 强隔离。

回答按「逃逸机理 → 拦截实现(入口替换 + 上下文注入)→ 边界(间接 eval、性能、分析成本)→ 纵深(CSP 叠加、强隔离)」组织,核心是「eval/Function 拦截是兼容层措施,安全兜底靠 CSP 与强隔离」。

#
★★

29. Shadow DOM 的 :host、::slotted 在组件样式封装的工程应用

Shadow DOM 的 :host 与 ::slotted 在组件样式封装中如何使用?有哪些工程应用与注意点?

  • :host 的语义:宿主元素样式与外部定制的接口
  • ::slotted 的语义:slot 内容的样式限定
  • 工程应用:主题定制、可组合组件与穿透治理

:host 选择器作用于 shadow 组件所在的宿主元素:shadow 内样式无法影响宿主外部,但 :host 让组件「自管宿主样式」(布局、尺寸、边框)——宿主元素的样式来自外部样式表 + 组件自身 :host 规则共同决定;:host(.class) / :host-context(selector) 允许组件根据「宿主环境」(外部类、上下文)调整自身样式,是组件感知外部主题的通道。::slotted(selector) 作用于「被分配到 slot 的内容」:外部传入的 DOM(插槽内容)不属于 shadow 树,外部样式对其可见,但 shadow 内样式默认管不到它——::slotted 让组件「限定插槽内容的样式」(如列表项布局、图片尺寸),同时保留外部对插槽内容的样式控制权。工程应用:主题定制——设计系统组件用 CSS 变量 + :host 暴露定制点(外部设置 --ds-primary,组件内部消费),避免「用 !important 与内部样式搏斗」;可组合组件——复杂组件用 slot 组合 + ::slotted 约束传入内容的结构样式,组件「封装结构、开放插槽」;穿透治理——把「必须可被外部定制」的点显式设计为变量/::part 接口,其余全部封装,减少 hack。注意点::host 的样式特异性与外部样式竞争(外部优先级更高,符合「外部可控」预期);::slotted 只能选择「直接的 slot 分配内容」不能深入后代;:host-context 已渐被推荐用 CSS 变量替代(性能与可维护性);微前端中「容器 shadow + 子应用样式」的组合:子应用内组件用 :host/::slotted 的定制模式与基座主题变量协作,形成「容器级隔离 + 组件级可定制」。

回答按「:host 与 ::slotted 的语义 → 工程应用(主题、组合、穿透治理)→ 注意点(特异性、限制、替代)」,核心是「封装与定制的平衡靠显式接口(变量、part、slotted)而非穿透 hack」。

#
★★

30. 微前端子应用的 window/document 代理边界场景,Symbol 属性、不可配置属性与 delete 操作的代理陷阱

window/document 代理在 Symbol 属性、不可配置属性与 delete 操作上有哪些代理陷阱?如何处理?

  • Symbol 键访问的代理盲区与 Symbol.for 共享
  • 不可配置属性的代理限制(window.window 等)
  • delete 操作与属性描述符的代理语义(deleteProperty、getOwnPropertyDescriptor)

三类陷阱:Symbol 属性——Proxy 的 get/set 陷阱默认拦截字符串键,Symbol 键的访问要走「Symbol 专用处理」(陷阱中检查 typeof key === 'symbol' 或显式实现),漏处理时 Symbol 读写直接落到真实对象;Symbol.for 的全局注册表跨 Realm 共享,沙箱内外同名 Symbol 可互相识别(信息通道);不可配置属性——window.window、window.self、window.top 等不可配置不可代理(proxy 无法改变其值语义),代理对这些键返回真实对象,子应用可借此直达真实全局;delete 操作——deleteProperty 陷阱需返回「删除是否成功」的准确语义(严格模式删除失败抛 TypeError),且对「代理不存在但真实存在」的键,删除语义要考虑「删沙箱内 vs 删真实对象」的歧义;getOwnPropertyDescriptor 陷阱的返回值必须与属性真实存在性一致(invariant),否则代理行为异常(Object.keys、in 运算、for-in 的结果错乱)。处理策略:Symbol——get/set 陷阱对 Symbol 键显式放行到值表或按白名单处理,Symbol.for 注册表做「按命名空间改写」(为沙箱加前缀);不可配置——对高危键显式拦截(返回受限的包装对象)或文档化「沙箱不覆盖此键」并配合强隔离兜底;delete——deleteProperty 中「沙箱内写入的键」允许删除,「真实全局的键」默认拒绝或返回 false(避免误删真实全局),并用 getOwnPropertyDescriptor 与 delete 语义一致化;工程上以「代理契约测试」覆盖这些边缘(断言 Symbol 键、不可配置键、delete 行为),防止沙箱升级回归。

回答按「三类陷阱的机制(Symbol/不可配置/delete 与描述符)→ 各自处理策略 → 契约测试兜底」组织,核心是「代理陷阱的正确性靠 invariant 一致,边缘场景要显式设计与测试」。

#

31. wujie(无界)的 iframe + web component 通信模型的真实性能成本与可观测性盲点

wujie(无界)的 iframe + WebComponent 通信模型有哪些真实性能成本与可观测性盲点?

  • 通信链路的性能成本:iframe 桥接、事件转发与渲染同步
  • 沙箱运行的成本:iframe 全局初始化与 DOM 挂载
  • 可观测性盲点:iframe 内监控、错误与性能数据的穿透

性能成本:iframe 创建与初始化——子应用 JS 在 iframe 全局中执行,iframe 环境初始化(文档、全局)有固定成本,频繁创建/销毁时显著;DOM 桥接——无界把 iframe 内渲染的 DOM 同步到主文档容器(shadowRoot),「iframe 内 DOM → 主文档 shadow DOM」的同步机制(样式、事件的桥接)有每帧/每次变更的拷贝与映射成本,频繁更新的大列表场景放大;通信——iframe 内与主文档的通信经内部桥接(非纯 postMessage 的定制通道),每次跨边界调用有包装与序列化成本。可观测性盲点:iframe 是独立执行环境,其内部错误、性能数据默认「看不见」——子应用报错若未经桥接通道上报,监控平台看不到 iframe 内的 JS 异常与资源耗时;Performance API 在 iframe 内是「iframe 自己的时间线」,主文档 RUM 拿不到子应用内部指标;调试链路断裂——DevTools 需切换到 iframe 上下文查看,生产监控需把 iframe 内数据显式桥接上报。工程应对:性能——评估「重建 vs 保活」的成本(高频切换用保活摊薄初始化)、监控桥接层开销(同步 DOM 的耗时占比)、对高频更新区域评估是否绕过桥接(直接在主文档渲染);可观测性——子应用内统一监控 SDK 经桥接通道上报(打上「iframe 内」来源标记)、性能指标双通道(iframe 内 PerformanceObserver + 桥接汇总)、错误捕获覆盖 iframe 全局(window.onerror 在 iframe 内同样挂载),把盲点显式补上。

回答按「性能成本(初始化、DOM 桥接、通信)→ 可观测性盲点(错误、性能、调试)→ 工程应对(保活、桥接监控、双通道)」,核心是「用 iframe 换隔离,就要补 iframe 的成本与盲点」。

#

32. 在子应用独立部署的 CI 中,如何校验主应用与子应用接口契约的版本兼容

子应用独立部署的 CI 中,如何校验主应用与子应用接口契约的版本兼容?

  • 契约的载体与版本化:类型、schema、事件协议
  • CI 校验机制:契约测试、schema 校验与组合构建
  • 兼容判定与发布门禁的联动

校验的输入是「契约的显式化」:子应用暴露给基座与兄弟应用的接口(props 参数、事件协议、MF exposes 的导出类型、路由契约、共享状态字段)定义为版本化契约(TypeScript 类型 + JSON Schema + 事件文档),随子应用版本发布。CI 校验机制:契约测试——主应用与消费方基于契约生成的 stub 执行验证(Pact 式:消费者契约 vs 提供方验证),子应用 CI 中跑「本版本契约 vs 已发布消费方契约」的兼容验证;schema 校验——接口数据结构用 JSON Schema 声明,CI 对比新旧版本 schema 判定 breaking(字段删除、必填新增、类型变化 = breaking;字段新增、可选化 = 兼容);组合构建——预发流水线用「最新主应用 × 候选子应用」与「旧主应用 × 候选子应用」组合跑集成冒烟,验证「升级后兼容矩阵中的既有组合」不破坏。门禁联动:breaking 判定驱动发布策略——兼容变更自动放行、breaking 变更要求同步升级消费方(发通知、生成升级任务)或走「兼容窗口」(新旧并存期间支持两版契约),否则阻断发布;契约版本号与语义化版本绑定(major 变更 = breaking,触发全量校验)。工程要点:契约单一来源(生成类型与 schema 的同源仓库)、契约变更记录(谁改了什么、影响谁)、校验结果进发布报告(可审计)。

回答按「契约显式化与版本化 → CI 三层校验(契约测试、schema 判定、组合冒烟)→ 门禁与 breaking 流程」组织,核心是「兼容校验的前提是契约显式,判定要自动、流程要门禁」。

#

33. 如何避免与宿主业务全局变量冲突并设置白名单

如何避免子应用与宿主业务全局变量冲突?白名单机制如何设计?

  • 全局变量冲突的形态:同名覆盖、依赖顺序、命名空间
  • 白名单机制:允许写入的键集合、归属登记
  • 治理闭环:登记、lint、运行检查与审计

冲突形态:子应用与宿主声明同名全局变量互相覆盖(window.config、window.userInfo),顺序敏感(谁后加载谁生效);依赖宿主未声明变量的隐式契约(子应用读宿主变量,宿主未提供时崩溃);沙箱内变量外泄(沙箱值表与真实全局混用)。白名单机制:定义「全局键白名单」——允许子应用写入/读取的 window 键集合(基座声明的共享契约键 + 子应用自有的命名空间键),白名单之外的键访问被沙箱拦截、告警或写进隔离值表;每个白名单键登记「归属 + 用途 + 类型」;子应用自己的全局键按命名空间(__APP1_xxx)登记,宿主键(__HOST_xxx)只读白名单。治理闭环:登记——接入清单中声明子应用需要的全局键;lint/静态检查——禁止代码中出现未登记的裸全局变量(window. 无前缀赋值);运行检查——沙箱代理对「非白名单键的写」抛警告/隔离,对「读未定义键」记录并上报;审计——加载前后全局快照 diff 与白名单比对,未登记的新增全局键即告警;宿主侧同样治理(宿主不在子应用命名空间内写键)。落地顺序:先登记(契约先行)→ 再约束(lint + 沙箱拦截)→ 后审计(diff 告警),三层闭环让「全局变量」从失控变为受管。

回答按「冲突形态 → 白名单机制(集合 + 归属登记)→ 治理闭环(登记、lint、运行检查、审计)」组织,核心是「全局变量治理 = 显式声明 + 边界强制 + 差异审计」。

#

34. Service Worker 在缓存与请求拦截的隔离工程实践

Service Worker 在微前端的缓存与请求拦截中如何隔离?有哪些工程实践?

  • SW 的作用域与注册隔离(scope 按路径、同源限制)
  • 多子应用 SW 的冲突:缓存键、拦截规则与版本
  • 工程实践:命名空间缓存、版本管理与降级

Service Worker 的隔离基础:SW 受同源与 scope 限制(scope 默认限于注册路径下),同源下多个 SW 不可并存(一个 origin 同一 scope 只有一个 active SW),这决定了「主子应用各自注册 SW」会互相覆盖(后注册的接管整个 origin 的 fetch 事件)。冲突形态:缓存键冲突(同 URL 不同内容)、拦截规则互相覆盖(各应用各自的缓存策略)、SW 版本与应用的版本不同步(缓存了旧产物)。工程实践:统一 SW 治理——同源下由基座(或平台统一入口)注册唯一 SW,缓存策略按「路径前缀/应用命名空间」分发(/apps/app1/* 走 app1 的缓存规则、remoteEntry 与入口不缓存或短缓存),子应用不各自注册;缓存键版本化——缓存名含版本(cache-v1-app1),发布时按版本清理旧缓存,避免脏缓存;remoteEntry 特殊处理——联邦入口禁止长缓存(no-cache/短缓存),否则发布后用户吃到旧 remoteEntry 指向已删除的旧产物;预缓存与离线——关键子应用产物进预缓存支持离线/弱网降级,但要与版本发布联动(precache 清单随版本更新);降级与清理——SW 异常时提供「跳过 SW 直连网络」的兜底(registration.unregister / 请求 bypass),配合缓存清理 API;测试——SW 的缓存行为纳入 E2E(模拟首次访问、版本升级、旧缓存残留),防止「缓存导致的问题」上线。

回答按「SW 的 scope/同源约束 → 多应用冲突形态 → 统一 SW + 命名空间缓存 + 版本化治理 → 降级与测试」组织,核心是「同源 SW 只能有一个,缓存治理按命名空间与版本协同」。

#

35. Trusted Types 在跨 iframe 注入 XSS 防御中的作用以及与 CSP nonce 的协作

Trusted Types 在跨 iframe 注入 XSS 防御中起什么作用?与 CSP nonce 如何协作?

  • Trusted Types 对 DOM XSS sink 的约束与跨 iframe 覆盖
  • CSP nonce 对脚本执行的授权机制
  • 二者协作:注入约束 + 执行授权的纵深防线

Trusted Types 的作用面是「DOM 注入点」:CSP 的 require-trusted-types-for 'script' 强制 innerHTML、document.write、script 文本等 sink 只能接收策略对象——任何第三方 iframe 向父页面注入的 HTML/脚本(经 DOM 写入)同样被约束(Trusted Types 是文档级策略,对 iframe 内外的注入点都生效,前提是文档设置了策略),把「注入的可执行内容」从「字符串即代码」变为「必须过策略校验」。CSP nonce 的作用面是「脚本执行授权」:script-src 中 nonce/hash 白名单决定「哪些脚本可以执行」,即使内容被注入 DOM,没有合法 nonce 的脚本标签也不会执行——nonce 每响应一值(防猜测)且对动态注入的脚本无效(nonce 由服务器在响应中生成,注入者无法预知)。协作关系:Trusted Types 管「代码进入 DOM 的通道」(防注入内容进入执行路径),nonce 管「代码是否可执行」(兜底未经过滤的执行路径);二者是「入口约束 + 出口授权」的纵深——Trusted Types 拦在注入动作(改不了 DOM 里的 HTML),nonce 拦在执行时机(即使 HTML 进去了,script 也跑不起来)。跨 iframe 场景:iframe 内容不可信时,父页面策略(Trusted Types + nonce + sandbox)决定其注入与执行能力;父页面自身从 iframe 收到消息后写 DOM,也要过 Trusted Types 策略(不信任 postMessage 数据直接 innerHTML)。工程落地:策略统一配置(CSP 头 + meta 兜底)、违规先 report-only 收集再 enforced、Trusted Types 策略工厂 + nonce 生成注入链路集成到模板引擎。

回答按「Trusted Types 管注入通道、nonce 管执行授权 → 跨 iframe 的覆盖与纵深关系 → 落地(report-only 过渡、策略工厂)」,核心是「双防线分别锁死『写入』与『执行』两个环节」。

#

36. 前端代码如何做能力边界与权限隔离

前端代码如何做能力边界与权限隔离?工程上有哪些实践?

  • 能力边界:代码可访问的资源与 API 的界定
  • 权限隔离:用户权限在 UI 与行为层的约束
  • 工程实践:最小权限、注入式能力、白名单与审计

能力边界指「代码运行时能做什么」的界定:可访问的 API(数据接口)、可操作的存储(localStorage/IndexedDB/Cookie)、可发起的请求(域名白名单)、可加载的资源、可执行的动态代码——工程上通过「最小权限」建模:子应用/模块只获得其业务必需的能力(基座注入受限的 API 客户端,而非让子应用自由 fetch 任意域)。权限隔离(用户权限)指「用户能做什么」:权限模型在基座统一(角色 → 权限点 → 路由/按钮/数据范围),子应用按契约消费权限声明渲染与拦截,服务端做最终校验(前端权限只是体验约束,不能作为安全边界)。工程实践:能力注入——子应用通过 props/上下文获取受限能力(请求实例带白名单域校验、存储代理限定键空间),代码拿不到「裸全局能力」;白名单——请求目标、动态代码执行、存储键、iframe 创建按白名单;权限 SDK——统一权限组件/指令(按钮级显隐、路由守卫),权限变更事件驱动刷新;审计——能力调用记录(谁调了什么 API)、权限变更日志;隔离——低信任代码(第三方嵌入)用沙箱/iframe 限制其能力面。要点:能力边界防「代码滥用」(XSS 后扩散面),权限隔离防「用户越权」,两者是不同维度,但都靠「注入 + 白名单 + 审计」落地。

回答按「能力边界(代码能做)vs 权限隔离(用户能做)→ 实践(注入式能力、白名单、权限 SDK、审计)→ 安全定位」组织,核心是「能力收敛靠注入与白名单,权限约束靠服务端兜底」。

#

37. CSP(Content Security Policy)三级策略(Report-Only / Enforced / Strict)

CSP 的三级策略(Report-Only / Enforced / Strict)如何设计与演进?各阶段的作用是什么?

  • 三级策略的语义:观察、执行、严格化
  • 演进流程:收集违规 → 修复 → 强制 → 严格
  • 微前端场景的 CSP 配置要点

三级策略是「渐进收紧」的路径:Report-Only——CSP 头只报告不拦截(Content-Security-Policy-Report-Only),页面正常跑、违规行为上报,用于收集真实违规(哪些内联脚本、哪些动态执行、哪些资源没白名单)评估策略可行性;Enforced——策略正式执行(Content-Security-Policy),违规被拦截,进入稳定运行;Strict——进一步收紧(去 unsafe-inline/unsafe-eval、非必要白名单域名移除、启用 require-trusted-types-for 等),把「宽策略」升级为「严格策略」。演进流程:先 report-only 观察 N 个周期(收集与修复),确认违规清零或已处理再切换 enforced;严格化逐项推进(每项先 report 再 enforce),避免「一次全严」引发生产事故;报告机制保留(enforced 后继续 report 收集漏网与绕过探测)。微前端场景的要点:远程资源白名单——子应用的 CDN 域、remoteEntry、chunk 必须加入 script-src/style-src;import-html-entry 与内联脚本——qiankun 等方案注入执行内联代码与严格 CSP 冲突,需 nonce 机制配合(服务器响应生成 nonce,运行时注入的脚本带 nonce)或改走模块通道;动态执行——沙箱/联邦的 eval/Function 使用需明确是否保留 unsafe-eval(保留则记为风险项);frame-src/worker-src——iframe 子应用与 Worker 的源声明。工程上「CSP 配置模板 + 违规报表 + 变更评审」纳入发布流程。

回答按「三级语义(观察/执行/严格)→ 渐进演进流程(先报告后强制、逐项收紧)→ 微前端配置要点(远程域、nonce、动态执行)」组织,核心是「CSP 落地是渐进工程,不是一次配置」。