媒体与设备平台 API

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

1. SharedArrayBuffer + COOP/COEP 跨域隔离的启用条件

使用 SharedArrayBuffer 需要满足哪些跨域隔离条件?COOP/COEP 应如何配置?无法启用隔离时有哪些降级方案?

  • SharedArrayBuffer 只能在跨域隔离上下文中使用
  • COOP/COEP 响应头的配置方式与作用
  • 无法启用隔离时的能力受限与降级策略

为防范 Spectre 侧信道攻击,浏览器要求 SharedArrayBuffer 只能在跨域隔离(cross-origin isolated)的顶层上下文中创建,判定标志为 window.crossOriginIsolated === true。启用方式:顶层文档返回 COOP: same-origin,把文档与跨源弹出窗口在进程层面隔离;同时返回 COEP: require-corp,强制所有跨源子资源声明 CORS 或 CORP 头,否则加载失败;被嵌入资源可用 CORP: same-origin/cross-origin 声明可被哪些源引用。满足后即可在主线程与 Worker 间共享内存,使用 Atomics 原语及 performance.measureUserAgentSpecificMemory。若因第三方跨源资源无法加头而无法隔离,需降级:改用 postMessage 复制传递数据,或通过同源代理/iframe 聚合资源后再启用隔离,避免依赖 SharedArrayBuffer 的强并行方案。

考察对安全上下文与跨域隔离机制的理解:SharedArrayBuffer 的可用性以 crossOriginIsolated 为门槛,核心是 COOP 与 COEP 响应头的组合配置;同时需掌握无法启用隔离时的工程降级方案,例如改用 postMessage 复制数据或通过同源代理聚合资源,体现对浏览器安全模型与部署约束的完整认知,也是排查共享内存类问题的第一步。

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: cross-origin
#
★★★

2. WebSocket、WebTransport、WebRTC 的协议层级与适用场景

WebSocket、WebTransport 与 WebRTC 分别建立在什么协议层级之上?各自适合哪些应用场景?

  • WebSocket 基于 TCP 的单连接全双工消息通道
  • WebTransport 基于 HTTP/3 与 QUIC 的流与数据报
  • WebRTC 基于 UDP 的点对点实时媒体传输

三者在协议栈与适用场景上差异明显。WebSocket 建立在 TCP 之上,经 HTTP Upgrade 握手后维持一条全双工消息通道,消息有序可靠、代理兼容性好,适合聊天、推送、协作编辑等中低频率双向通信,但受 TCP 队头阻塞与握手延迟限制。WebTransport 基于 HTTP/3 的 QUIC,提供多条相互独立的流与不可靠数据报,支持可靠与尽力投递并存、连接迁移,延迟更低,适合实时游戏、流媒体与高频遥测,但需要 HTTP/3 环境支持。WebRTC 面向实时音视频,媒体经 UDP + SRTP 传输,自带 ICE/STUN/TURN 打洞与 NAT 穿透,延迟最低,适合音视频通话与直播,但需要自建信令服务且复杂度高。选型时应按延迟要求、可靠性需求、部署环境与生态成熟度综合权衡。

考察对三类实时通信协议层级的定位与选型能力:WebSocket 在 TCP 之上保证有序可靠,WebTransport 借 HTTP/3 与 QUIC 提供多路流与数据报的混合传输,WebRTC 则用 UDP 与 SRTP 实现低延迟媒体;理解各自的延迟、可靠性与部署成本,才能在实时聊天、游戏同步与音视频通话中做出正确的技术决策。

const wt = new WebTransport('https://example.com:4433/demo');
await wt.ready; // 握手完成后可用
const writer = wt.datagrams.writable.getWriter();
writer.write(new Uint8Array([1, 2, 3]));
#
★★★

3. Cross-Origin Isolation(COOP/COEP/CORP)

COOP、COEP、CORP 三个响应头分别解决什么问题?三者如何配合实现跨域隔离?

  • COOP 隔离顶层窗口与跨源窗口的引用关系
  • COEP 约束文档内跨源子资源的加载策略
  • CORP 由资源方声明可被嵌入的权限范围

三者共同构成跨域隔离的三道配置。COOP(Cross-Origin-Opener-Policy)由顶层文档返回,取值 same-origin、same-origin-allow-popups 或 unsafe-none,决定跨源窗口能否通过 window.opener 相互引用,实现进程级隔离。COEP(Cross-Origin-Embedder-Policy)要求文档加载的所有跨源子资源(脚本、图片、iframe 等)要么以 CORS 方式获取,要么携带 CORP 响应头,否则加载失败,从而保证同源上下文内部不存在跨源数据侧信道。CORP(Cross-Origin-Resource-Policy)由资源提供方声明 same-origin、same-site 或 cross-origin,控制资源可被哪些源嵌入。三者配合且文档与所有子资源满足条件后,window.crossOriginIsolated 为 true,才能启用 SharedArrayBuffer、performance.measureUserAgentSpecificMemory 与高精度定时器等能力。

考察对安全响应头体系的区分与配合:COOP 管理顶层窗口与跨源窗口的引用隔离,COEP 约束文档内跨源子资源的加载方式,CORP 由资源方声明可被嵌入的权限,三者协同消除跨源侧信道;理解各头的方向与取值,才能正确配置跨域隔离并启用 SharedArrayBuffer 等高阶能力。

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: cross-origin   # 被嵌入资源可声明
#
★★★

4. Service Worker 的 install/activate/fetch 事件驱动的离线优先策略

Service Worker 的 install、activate、fetch 事件分别承担什么职责?如何基于它们实现离线优先?

  • install 阶段预缓存应用壳资源
  • activate 阶段清理旧缓存并接管页面控制
  • fetch 事件拦截请求实现缓存优先策略

Service Worker 是运行在独立线程、由事件驱动的网络代理。install 阶段适合预缓存:在 install 事件里打开 Cache Storage 写入 app shell 静态资源,并用 event.waitUntil 保证异步缓存完成,任一请求失败则安装中止。activate 阶段发生在旧版本退出、新版本接管之前,适合清理与当前版本不符的旧缓存,并调用 clients.claim() 让已打开的页面尽快被接管。fetch 事件拦截同作用域内的所有请求,离线优先策略通常分层实现:缓存命中直接返回;未命中再请求网络并在成功后写入缓存;导航请求可回退到缓存的 app shell;同时结合后台同步与消息推送补齐离线能力。关键是正确使用 waitUntil 管理生命周期,避免安装或激活未完成就被跳过,并用版本号隔离缓存。

考察 Service Worker 生命周期事件与缓存策略的关系:install 负责预缓存应用壳,activate 负责清理旧版本并接管页面,fetch 负责拦截请求并按缓存优先策略决策;正确使用 waitUntil 管理异步任务、用版本号隔离缓存,是构建离线优先应用的骨架,也是常见的故障排查切入点。

self.addEventListener('fetch', (e) => {
  e.respondWith(
    caches.match(e.request).then((hit) =>
      hit || fetch(e.request).then((res) => {
        const copy = res.clone();
        caches.open('v1').then((c) => c.put(e.request, copy));
        return res;
      })
    )
  );
});
#
★★★

5. WebTransport 的 stream 与 datagram 在低延迟游戏与实时音视频的取舍

WebTransport 的 stream 与 datagram 分别适合传输什么数据?在低延迟游戏与实时音视频中应如何取舍?

  • stream 提供可靠有序的多路流传输
  • datagram 提供不可靠低延迟的尽力投递
  • 游戏状态与音视频数据对可靠性与时效性的不同要求

WebTransport 提供两类通道。Stream 基于 QUIC 流,可靠且按序(也可按需部分可靠),适合对完整性要求高的数据,如登录鉴权、玩家输入确认、存档同步与排行榜;Datagram 类似 UDP,无重传、尽力投递,延迟最低但可能丢包乱序,适合高频且可容忍丢失的状态快照、语音帧与遥测。低延迟游戏中通常混合使用:角色位置、操作输入等高频数据走 datagram 以最小化延迟,缺失帧由客户端插值补偿;关键事件与控制命令走 stream 保证送达。实时音视频中媒体帧本身对延迟敏感、可容忍丢包,适合 datagram 或不可靠流,而信令、码率重协商与控制消息走可靠流。取舍的核心是区分"可丢"与"不可丢"数据,按数据的时效性与完整性要求选择通道。

考察 WebTransport 双通道语义的理解与工程取舍:stream 可靠有序适合登录鉴权、输入确认等关键数据,datagram 不可靠低延迟适合高频位置状态与媒体帧;低延迟游戏与实时音视频应区分可丢与不可丢数据混合使用,并配合插值补偿与重传策略,这是实时应用架构设计的核心。

#
★★★

6. Atomics.waitAsync/Atomics.notify 在 Worker 间锁与唤醒的工程价值

Atomics.waitAsync 与 Atomics.notify 如何实现 Worker 间的锁与唤醒?相比同步的 Atomics.wait 有什么优势?

  • Atomics.wait 只能阻塞等待,无法用于主线程
  • waitAsync 以 Promise 形式异步等待,不阻塞调用线程
  • compareExchange 竞争标志位与 notify 唤醒的锁实现

共享内存同步原语允许 Worker 通过 SharedArrayBuffer + Int32Array 协作。Atomics.wait 会同步阻塞调用线程,因此只能用于 Worker 线程,绝不能在主线程使用;Atomics.waitAsync 以 Promise 形式等待,可在主线程调用而不阻塞 UI。Atomics.notify 用于唤醒指定数量的等待者。二者组合可实现互斥锁、信号量与条件变量:加锁时用 Atomics.compareExchange 竞争标志位,成功则获得锁,失败则 waitAsync 挂起等待;释放锁时写回 0 并 notify 唤醒等待者。工程价值在于避免 postMessage 的序列化与拷贝开销,实现微秒级同步,并让主线程以非阻塞方式参与并发协调。使用中需处理假唤醒与超时:被唤醒后循环重新检查条件,等待时传入超时时间。

考察共享内存同步原语的使用边界与价值:Atomics.wait 同步阻塞只能用于 Worker,waitAsync 以 Promise 形式适配主线程而不阻塞 UI;compareExchange 竞争标志位与 notify 唤醒组成互斥锁和条件变量,可避免 postMessage 的拷贝开销,同时需处理假唤醒与超时才能保证并发正确性。

const sab = new SharedArrayBuffer(4);
const lock = new Int32Array(sab);
async function acquire() {
  while (Atomics.compareExchange(lock, 0, 0, 1) !== 0) {
    await Atomics.waitAsync(lock, 0, 1).value; // 等待被唤醒
  }
}
function release() { Atomics.store(lock, 0, 0); Atomics.notify(lock, 0, 1); }
#
★★★

7. WebTransport 基于 HTTP/3 + QUIC 的双向流、Datagram 与连接迁移

WebTransport 如何基于 HTTP/3 与 QUIC 提供双向流和数据报?连接迁移是什么?它带来什么价值?

  • WebTransport 与 HTTP/3、QUIC 的层次关系
  • 双向流与 Datagram 的 API 形态及语义
  • QUIC 连接 ID 与网络切换时的连接迁移

WebTransport 是运行在 HTTP/3 之上的传输 API,底层由 QUIC 提供多路复用、加密与拥塞控制。它暴露两类通道:createBidirectionalStream() 创建双向流,支持可靠有序传输,且多流相互独立,单条流阻塞不影响其他流,从而缓解 TCP 队头阻塞问题;sendDatagram() 与 readable 提供不可靠、低延迟的数据报通道。QUIC 的连接迁移指连接以 64 位连接 ID 标识而非 IP:端口四元组,当移动设备在 Wi-Fi 与蜂窝网络间切换时,连接 ID 不变,数据流可无缝继续而无需重新握手,对实时音视频与游戏尤其有价值。工程上还需处理流关闭、背压控制与服务器推送,并通过 session.ready 等待握手完成后再发送数据。

考察 WebTransport 底层机制的理解:HTTP/3 之上的 QUIC 提供多路独立流与数据报,缓解 TCP 队头阻塞;连接以连接 ID 标识而非四元组,网络切换时连接迁移无需重新握手;掌握这些机制并结合 ready 等待与背压控制,才能设计出稳定低延迟的实时通信应用。

const session = new WebTransport('https://example.com:4433/demo');
await session.ready;
const stream = await session.createBidirectionalStream();
const writer = stream.writable.getWriter();
await writer.write(new TextEncoder().encode('ping'));
#
★★★

8. WebRTC 信令、SDP、ICE/STUN/TURN 的建立流程

描述 WebRTC 从发起方到通话建立的完整流程:信令、SDP、ICE、STUN、TURN 各扮演什么角色?

  • 信令服务器负责交换 offer/answer 与控制消息
  • SDP 描述媒体能力、编解码与候选信息
  • ICE 候选收集、STUN 打洞与 TURN 中继兜底

WebRTC 建立连接分四步。第一步信令:双方通过信令服务器(如 WebSocket)交换会话描述与控制消息,信令协议不在 WebRTC 规范内,由应用自行实现。第二步 SDP 协商:发起方 createOffer() 生成 offer,包含媒体类型、编解码器、ICE 候选与指纹,经信令发送给对端;对端 createAnswer() 生成 answer,双方 setLocalDescription/setRemoteDescription 完成协商。第三步 ICE:双方收集主机地址、STUN 反射地址与 TURN 中继地址三类候选,通过 trickle ICE 持续交换并做连通性检查。第四步穿透与中继:STUN 帮助获取公网映射地址实现 NAT 穿透;穿透失败时由 TURN 服务器中继媒体流量兜底。连接建立后媒体经 SRTP 加密传输,DTLS 负责密钥协商与身份认证。

考察 WebRTC 全流程架构的掌握:信令负责协商并交换 SDP,SDP 描述媒体与网络能力,ICE 收集并探测候选地址,STUN 实现 NAT 穿透,TURN 在穿透失败时中继兜底;分清四者职责与先后顺序,是定位连接失败、媒体不通与延迟异常等问题的重要前提。

const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// 通过信令通道把 offer 发给对端,并接收 answer
#
★★★

9. Page Visibility、Lifecycle API 与后台节流对定时器的影响

Page Visibility API 与 Page Lifecycle API 如何反映页面状态?浏览器后台节流对 setTimeout/setInterval 与 requestAnimationFrame 有什么影响?

  • document.visibilityState 与 visibilitychange 的语义
  • Page Lifecycle 的 frozen/discarded 状态与 freeze/resume 事件
  • 后台定时器降频与 rAF 暂停的节流策略

Page Visibility API 通过 document.visibilityState(visible/hidden)与 visibilitychange 事件反映页面是否对用户可见,是判断用户是否真正看到页面的标准方式。Page Lifecycle API 进一步定义完整状态机:active、passive、hidden、frozen、terminated、discarded,其中 frozen 表示页面被冻结(定时器与任务暂停,可恢复),discarded 表示被销毁回收;onfreeze、onresume 与 pageshow/pagehide 用于感知状态转移。浏览器对后台页面实施节流:hidden 状态下 setTimeout/setInterval 被大幅降频(Chrome 合并到约每分钟一次),requestAnimationFrame 完全暂停,以减少 CPU 与电量消耗。因此后台播放、轮询与心跳不应依赖定时器,可改用 Web Worker 计时,或根据 visibility 状态动态调整轮询频率。

考察页面生命周期与后台节流的联动:visibilityState 决定页面是否可见,freeze 决定是否被冻结,两者都会影响定时器精度与 rAF 触发;据此在 hidden 时暂停轮询、清理定时器,在可见时恢复并刷新数据,可避免埋点失真与电量浪费,是移动端体验优化的基础能力。

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    clearInterval(timer); // 暂停轮询
  } else {
    timer = setInterval(poll, 5000); // 恢复
  }
});
#
★★★

10. Navigation API(navigation.navigate、navigation.intercept、navigation.currentEntry,已纳入 Baseline 且主流浏览器可用)替代 History API 的能力差异

Navigation API 相比 History API 提供了哪些新能力?navigation.navigate、navigation.intercept、navigation.currentEntry 分别解决什么问题?

  • navigate 的编程式导航与结构化 state
  • intercept 对导航的拦截与异步接管
  • currentEntry/entries 对会话历史的建模与恢复

Navigation API 是对 History API 的现代化替代,已纳入 Baseline 且主流浏览器可用。核心对象 navigation 提供三类能力:navigation.navigate(url, { state, history }) 以编程方式发起导航并携带结构化 state,替代 pushState/replaceState 的分散调用;navigation.intercept() 在 navigate 事件中拦截导航,可返回 Promise 异步接管,实现过渡动画、取消导航与数据预取,这是 History API 无法原生完成的;navigation.currentEntry 表示当前历史条目,含 key、id、url、state 与 getState(),entries() 可遍历整个会话历史,便于前进后退识别与路由状态恢复。相比 History API,它区分同文档与跨文档导航,提供 onnavigate、oncurrententrychange 等事件,替代手工维护 popstate 加 location 的脆弱组合,并统一了 SPA 与 MPA 的导航模型。

考察新一代导航原语的能力边界:navigate 提供编程式导航与结构化 state,intercept 支持拦截与异步接管,currentEntry 与 entries 提供可遍历的会话历史;三者弥补 History API 在 SPA 路由状态管理上的不足,理解其事件模型与 API 语义是迁移选型与工程落地的依据。

navigation.addEventListener('navigate', (e) => {
  if (e.canIntercept && e.destination.url.startsWith(location.origin)) {
    e.intercept({ handler: async () => { await loadRoute(e.destination.url); } });
  }
});
#
★★★

11. AVIF 与 JPEG XL 的浏览器支持、解码性能与生产管线集成

AVIF 与 JPEG XL 的浏览器支持现状如何?在解码性能与生产管线集成上各有什么特点?

  • AVIF 基于 AV1 帧内编码的压缩效率与支持范围
  • JPEG XL 的压缩与渐进解码优势及支持受限现状
  • 生产管线中的格式选型、参数调优与降级策略

AVIF 基于 AV1 帧内编码,压缩率显著优于 JPEG 与 WebP,支持 HDR、透明通道与无损模式,Chrome、Firefox、Safari 均已支持,已成为生产环境主流的下一代图片格式。JPEG XL 压缩率与 AVIF 相当或更优,支持无损、渐进解码、超大尺寸与多色域,但 Safari 曾宣布支持后撤回,主流浏览器未广泛启用,目前主要用于 JPEG 无损转码与存档场景。解码性能上,AVIF 编码较慢,解码在支持硬件加速时表现良好;JPEG XL 解码复杂度适中。生产管线要点:用 提供 AVIF→WebP→JPEG 多源降级;构建时按感知质量指标选参并控制编码耗时;CDN 做协商式转码与缓存;对 JPEG XL 采用特性检测渐进增强,避免阻塞关键路径。

考察下一代图片格式的工程认知:AVIF 基于 AV1 压缩率高且主流浏览器支持,已可生产落地;JPEG XL 虽性能优异但浏览器支持受限,多用于无损转码与存档场景;生产环境应以 picture 多源降级、按感知质量选参并结合 CDN 协商转码,兼顾兼容性与加载性能。

<picture>
  <source type="image/avif" srcset="img.avif">
  <source type="image/webp" srcset="img.webp">
  <img src="img.jpg" alt="示例图片">
</picture>
#
★★★

12. pageshow/pagehide 与 visibilitychange 在 bfcache 恢复场景的事件触发差异

页面进入或离开 bfcache 时,pageshow/pagehide 与 visibilitychange 的触发顺序和差异是什么?如何判断页面是从 bfcache 恢复的?

  • 离开页面时 pagehide 与 visibilitychange 的触发顺序
  • 恢复页面时 pageshow 与 visibilitychange 的触发顺序
  • event.persisted 属性对 bfcache 进出的识别

页面被放入 bfcache 或从中恢复时,事件触发顺序有明确差异。离开页面进入 bfcache 时,依次触发 pagehide(persisted=true)与 visibilitychange(变为 hidden),且不会触发 unload;普通导航离开时则还会触发 unload。从 bfcache 恢复时,页面不重新执行脚本初始化,依次触发 pageshow(persisted=true)与 visibilitychange(变为 visible),且不会重新触发 DOMContentLoaded。因此判断恢复来源必须使用 pageshow 事件的 persisted 属性,visibilitychange 无法区分"从后台切回"与"从 bfcache 恢复"。工程意义:恢复时需重建 WebSocket、恢复定时器与播放状态、刷新过期数据;清理逻辑与恢复逻辑应挂载在 pagehide/pageshow 上而非依赖 load 或 unload。

考察 bfcache 生命周期事件的语义差异:离开页面时 pagehide 先于 visibilitychange 触发,恢复时 pageshow 先于 visibilitychange 触发,persisted 标志用于识别缓存进出;visibilitychange 无法区分后台切换与 bfcache 恢复,掌握触发顺序才能正确重建连接、定时器与播放状态,并据此设计恢复逻辑避免重复初始化。

window.addEventListener('pageshow', (e) => {
  if (e.persisted) reconnectWebSocket(); // 从 bfcache 恢复
});
#
★★★

13. Navigation API 的拦截能力(navigation.intercept)与 History API 的能力对比

navigation.intercept 提供了什么拦截能力?与 History API 相比有哪些本质差异?

  • intercept 在 navigate 事件中拦截并异步接管导航
  • 可取消导航、延迟提交与旧 handler 自动中止
  • History API 只能事后响应 popstate,无法介入导航过程

navigation.intercept() 是 Navigation API 的核心增强,在 navigate 事件处理函数中调用即可拦截本次导航。它返回 Promise 并延迟导航的提交,开发者可在期间完成数据预取、权限校验、过渡动画或按需渲染后再真正切换视图;也可以调用 preventDefault() 取消导航,或在处理过程中改跳其他目标。相比 History API,popstate 只是导航发生之后的通知,无法在导航进行中介入,也无法区分导航来源与类型;而 intercept 提供了"先决策后提交"的控制点:handler 可返回 Promise,导航被新导航替换时浏览器会自动中止旧 handler,配合 transitionWhile 还能驱动 View Transitions。本质上 intercept 把导航从"状态事后同步"升级为"可编程的异步流程",是路由框架可以稳定依赖的标准原语。

考察拦截式导航的工程价值:intercept 在导航发起时提供决策点,支持异步接管、取消与旧 handler 自动中止,还可配合 transitionWhile 驱动视图过渡;History API 的 popstate 只能事后响应无法介入导航,理解两者差异是设计路由守卫、中间件与页面过渡的关键。

navigation.addEventListener('navigate', (e) => {
  if (!e.canIntercept) return;
  e.intercept({
    handler: async () => {
      await prefetchData(e.destination.url); // 异步接管
    },
  });
});
#
★★★

14. beforeunload 与 pagehide 在移动端的可靠性差异

beforeunload 与 pagehide 在移动端有什么可靠性差异?页面离开时应如何选择事件与上报方式?

  • beforeunload 的确认弹窗语义与移动端限制
  • pagehide 在卸载与 bfcache 场景下的稳定触发
  • sendBeacon/fetch keepalive 在离开时的可靠上报

beforeunload 的主要用途是弹窗提醒用户可能丢失未保存内容,浏览器要求页面必须被用户交互过才允许弹窗,移动端 Safari 等实现不一致,弹窗可能不出现或不可控;它也不适合做数据上报,因为触发在卸载确认之前且可能被用户取消。pagehide 在页面即将卸载或进入 bfcache 时触发,移动端更可靠:页面切到后台、被系统回收、进入 bfcache 等场景都会触发,且不依赖用户交互。工程上,离开时的上报应优先使用 pagehide 配合 navigator.sendBeacon() 或 fetch(url, { keepalive: true }),浏览器会在卸载阶段尽力送达请求;保存草稿等关键操作应在用户操作时主动完成,不能依赖卸载事件。unload 在现代浏览器中已不可靠,不应作为唯一兜底。

考察页面离开事件在移动端的可靠性:beforeunload 语义是拦截确认且受用户交互限制,pagehide 在卸载与进入 bfcache 时更稳定触发;离开场景的上报应优先使用 sendBeacon 或 keepalive 请求,unload 在现代浏览器中不可依赖,需组合多个事件才能保证数据可靠送达。

window.addEventListener('pagehide', () => {
  navigator.sendBeacon('/api/leave', JSON.stringify({ time: Date.now() }));
});
#
★★★

15. Document Lifecycle(DOMContentLoaded、load)与 Frame Tree 嵌套子文档的传播

DOMContentLoaded 与 load 事件何时触发?在 iframe 嵌套的 Frame Tree 中二者如何传播?

  • DOMContentLoaded 在 DOM 解析完成后触发
  • load 等待页面全部资源加载完成
  • 父文档 load 与子 iframe load 的先后关系及事件传播边界

DOMContentLoaded 在 HTML 文档解析完成、同步脚本执行完毕后触发,此时图片、样式表等子资源可能仍在加载;load 则等到页面所有资源(图片、样式、脚本、iframe 等)加载完成后才触发。对于嵌套 iframe 的 Frame Tree,每个文档独立触发自己的 DOMContentLoaded 与 load:父文档的 load 会等待所有子 iframe 完成加载(阻塞型 iframe),因此父文档 load 一定不早于子文档 load;而 DOMContentLoaded 只等待当前文档解析完成,不受子文档影响。事件不会跨文档冒泡传播,父文档无法直接收到子文档的 DOMContentLoaded,需通过 contentWindow 显式监听或由子文档用 postMessage 通知。理解该时序对首屏统计、埋点归因与资源预加载时机的设计很关键。

考察文档生命周期与嵌套文档的关系:DOMContentLoaded 只关心当前文档解析完成,load 等待全部资源;父文档的 load 会等待阻塞型子 iframe 加载完成,而 DOM 事件不会跨 Frame 冒泡;掌握该时序对首屏统计、埋点归因与资源预加载时机的设计至关重要,也是排查嵌套页面加载异常的切入点。

// 父文档监听子 iframe 的 load
iframe.addEventListener('load', () => {
  console.log('子文档资源加载完成');
});
#
★★★

16. View Transitions 的命名 view-transition 与双向/单向动画的精细化控制

View Transitions 中如何通过命名视图实现共享元素动画?如何用伪元素控制双向与单向动画?

  • view-transition-name 建立新旧状态元素的配对
  • ::view-transition-old/new 伪元素分别代表新旧快照
  • 通过 CSS 控制动画方向、时长与关键帧

View Transitions 在 DOM 状态切换时生成平滑过渡:document.startViewTransition(callback) 快照旧状态、执行 callback 更新 DOM、再快照新状态,浏览器用 ::view-transition、::view-transition-old()、::view-transition-new() 等伪元素驱动动画。默认动画是整页的交叉淡化;要给特定元素做位置与大小的联动,需要为该元素设置 view-transition-name: 某名字,新旧状态中同名元素会被浏览器配对,自动生成从旧位置到新位置的位移动画,实现"共享元素过渡"。双向/单向控制通过 CSS 完成:只保留 ::view-transition-old 或 ::view-transition-new 的动画即可让动画只在单一方向发生;用 animation 属性设置时长与缓动,用自定义 keyframes 精细控制每个命名组的时序;JS 侧可用 updateCallbackDone、ready、finished 三个 Promise 编排复杂流程。

考察 View Transitions 的命名与动画控制:view-transition-name 是建立新旧状态元素配对的关键,old 与 new 伪元素分别代表切换前后的快照;通过 CSS 动画属性与自定义关键帧可精细控制单向或双向过渡,配合 startViewTransition 返回的 Promise 编排复杂时序与清理逻辑。

.card { view-transition-name: card; }
::view-transition-old(card) { animation: fade-out 0.3s ease; }
::view-transition-new(card) { animation: fade-in 0.3s ease; }
#
★★★

17. Page Lifecycle API(onfreeze/onresume/visibilitychange)

Page Lifecycle API 中的 frozen 状态如何进入?onfreeze/onresume 与 visibilitychange 有什么关系?

  • frozen 是 hidden 之后更深的冻结状态
  • freeze 在冻结前触发,resume 在恢复时触发
  • 与 visibilitychange 的先后关系及冻结期间的能力限制

Page Lifecycle API 定义了完整的页面状态机,frozen 是 hidden 之后更深的暂停状态:浏览器为节省 CPU 与电量会冻结页面,暂停定时器、requestAnimationFrame、任务队列与网络活动,冻结前触发 freeze 事件(document.onfreeze),恢复时触发 resume 事件(document.onresume)。触发顺序上,页面先触发 visibilitychange(变为 hidden),随后才可能进入 frozen 并触发 freeze,因此 freeze 意味着页面不仅不可见还被冻结;resume 之后页面回到 hidden,用户重新看到页面时再触发 visibilitychange(变为 visible)。工程上,freeze 前应保存关键状态、关闭 WebSocket 与未完成的 IndexedDB 事务(冻结期间无法送达),resume 时重建连接并刷新数据。注意冻结期间定时器不执行,后台任务不能依赖 setTimeout,应使用 freeze/resume 事件驱动。

考察生命周期冻结语义与工程应对:freeze 在页面隐藏之后、冻结之前触发,resume 在恢复时触发,冻结期间定时器、网络与渲染全部暂停;因此应在 freeze 前保存状态并关闭连接,resume 时重建,同时配合 visibilitychange 区分可见性变化,避免后台任务空转与数据丢失。

document.addEventListener('freeze', saveState);
document.addEventListener('resume', () => { restoreConnections(); refreshData(); });
#
★★★

18. View Transitions API(同文档 document.startViewTransition() 与跨文档 @view-transition)的动画编排

同文档与跨文档 View Transitions 分别如何触发?@view-transition 规则如何启用跨文档过渡?

  • document.startViewTransition 驱动同文档过渡
  • @view-transition { navigation: auto } 启用跨文档过渡
  • 命名视图与伪元素在两类过渡中的编排

同文档 View Transitions 通过 document.startViewTransition(updateCallback) 触发,适合 SPA 路由切换:浏览器快照旧视图、执行 callback 更新 DOM、再快照新视图,并用伪元素生成过渡动画。跨文档 View Transitions 由浏览器在页面导航时自动触发,要求源页面与目标页面都声明 @view-transition { navigation: auto; } 规则,导航进行时新文档完成首帧渲染并与旧文档快照合成过渡动画。编排要点:用 view-transition-name 给元素命名,实现新旧文档同名元素的共享元素过渡;用 ::view-transition-old/new 伪元素与 keyframes 精细控制动画;同文档场景可用 Navigation API 的 transitionWhile 让拦截的导航与过渡协同,跨文档场景可在 pageswap 事件中做最后的快照与清理;两者都可以用 startViewTransition 返回的 ready、finished 等 Promise 编排复杂时序。

考察同文档与跨文档两种过渡形态:同文档过渡由 startViewTransition 触发并配合 Navigation API 的 transitionWhile 协同,跨文档过渡需源与目标页面声明 view-transition 规则;命名视图与 old/new 伪元素是动画编排的核心,理解两者差异才能实现统一且平滑的页面切换体验。

@view-transition { navigation: auto; }
document.startViewTransition(async () => { await renderRoute(); });
#
★★★

19. Back/Forward Cache(bfcache)对状态恢复与监听器的影响

bfcache 缓存页面时会保留哪些状态?对监听器、连接与定时器有什么影响?恢复时如何治理?

  • bfcache 保留完整 DOM 与 JS 堆状态
  • 冻结期间定时器、WebSocket 与网络不可用
  • 用 pageshow 的 persisted 属性重建状态

bfcache 会保存页面的完整状态:DOM 树、JS 堆、表单输入、滚动位置与渲染状态,用户前进/后退时瞬间恢复,不重新执行脚本初始化。但页面被冻结期间,定时器全部暂停、requestAnimationFrame 停止、WebSocket 连接断开、未完成的网络请求与上报可能丢失。治理要点:一是监听器与连接必须可恢复,不要依赖 load 一次性初始化,应监听 pageshow 并检查 event.persisted,在恢复时重建 WebSocket、重启定时器与播放器;二是冻结前用 pagehide 保存草稿与敏感状态,避免旧状态页面被呈现;三是避免注册 unload 监听器、打开 IndexedDB 连接等阻止进入 bfcache 的模式,保证缓存命中率。恢复后还需刷新过期数据、重新校验登录态与权限,防止展示陈旧内容。

考察 bfcache 的状态保留与恢复语义:缓存保留 DOM 与 JS 堆但冻结定时器与网络,恢复时不重新执行脚本;应监听 pageshow 并检查 persisted 重建 WebSocket 与定时器,同时避免注册 unload 监听器等阻止缓存的行为,保证前进后退的缓存命中率与用户体验。

window.addEventListener('pageshow', (e) => {
  if (e.persisted) { reconnect(); resumeTimers(); }
});
#
★★★

20. Prerender 文档对 analytics 页面浏览的过滤策略(避免重复埋点)

预渲染文档何时触发?预渲染期间脚本执行会造成什么问题?如何避免 analytics 重复埋点?

  • 预渲染在用户导航前提前加载并执行脚本
  • 预渲染上报造成虚假 PV 与时长失真
  • document.prerendering 与激活时机的过滤策略

浏览器为加速导航会预渲染页面:隐藏文档提前完成 HTML 解析、脚本执行与资源加载,用户真正点击导航时再激活(activation)。若埋点代码在脚本加载时直接上报"页面浏览",预渲染阶段就会产生一次虚假访问,用户激活后又会上报一次,造成 PV 翻倍与停留时长失真。过滤策略:用 document.prerendering 判断当前是否处于预渲染状态,若为 true 则暂缓上报;监听 pageshow 事件并检查 event.persisted 为 false 且 document.prerendering 为 false,或监听 pagevisibilitychange,仅在页面激活成为可见后才上报一次;可用 PerformanceNavigationTiming 的 activationStart 归因预渲染耗时。对电商与广告数据还需结合导航类型与 Referrer 过滤预取流量。核心原则是只有用户真正看到的页面才计入 PV 与转化。

考察预渲染对分析数据的污染与治理:预渲染阶段脚本已执行但用户未真正看到页面,若直接上报会产生重复 PV 与时长失真;应利用 document.prerendering 暂缓上报,在页面激活成为可见后仅上报一次,并结合 activationStart 归因耗时,保证埋点数据的准确与可信。

if (document.prerendering) {
  document.addEventListener('prerenderingchange', reportPageView, { once: true });
} else {
  reportPageView();
}
#
★★★

21. fetchLater() API 在页面销毁阶段可靠上报的应用,abort 与 activateAfter 的工程边界

fetchLater() 相比 sendBeacon 有什么不同?abort 与 activateAfter 参数如何影响上报的可靠性?

  • fetchLater 在页面销毁后仍由浏览器代发请求
  • activateAfter 控制请求的发送时机
  • abort 信号、请求体限制与安全上下文要求

fetchLater() 是 Chromium 提出的可靠上报 API:页面销毁(导航离开、关闭、进入 bfcache)后,浏览器仍会代表页面发出请求,弥补 sendBeacon 数据量小、unload 不可靠的短板。调用 fetchLater(url, { body, activateAfter, abort }) 登记一个延迟请求:activateAfter 指定请求最迟的发送时间(0 表示页面销毁时立即发送),浏览器还可在空闲时提前发送以保证送达;abort 是 AbortSignal,可在页面存活期间主动取消已登记的请求。工程边界:请求体仍有限制,不适合大文件与流式数据;需要安全上下文;fetchLater 只保证"发出"而不保证服务器处理成功,也不等待响应,因此服务端需配合幂等与去重;应把订单、关键埋点等少量关键数据用 fetchLater 登记,实时数据仍走普通 fetch。

考察新一代可靠上报机制的使用边界:fetchLater 在页面销毁后仍由浏览器代发请求,activateAfter 控制发送时机,abort 信号支持主动取消;它弥补 sendBeacon 体量小与 unload 不可靠的短板,但只保证发出不保证处理成功,服务端需配合幂等与去重机制保证最终一致。

fetchLater('/api/report', {
  body: JSON.stringify({ event: 'checkout', id: orderId }),
  activateAfter: 0, // 页面销毁时立即发送
});
#
★★★

22. WebCodecs VideoDecoder/VideoFrame 在低延迟视频编辑的工程价值

WebCodecs 的 VideoDecoder 与 VideoFrame 在低延迟视频编辑场景有什么工程价值?与 MSE 相比如何?

  • VideoDecoder 直接解码编码帧,应用控制时序
  • VideoFrame 与 Canvas/WebGL 的零拷贝协作
  • 相对 MSE 的精细控制与逐帧处理能力

WebCodecs 让 Web 应用直接访问底层编解码器:VideoDecoder.decode(EncodedVideoChunk) 把编码数据解码为 VideoFrame,输入输出队列与时间戳完全由应用控制,不经过 MSE 的缓冲与媒体管道,端到端延迟可降到几十毫秒。VideoFrame 是统一的视频帧抽象,可直接绘制到 Canvas、上传为 WebGL/WebGPU 纹理或送入 VideoEncoder 转码,携带 timestamp、duration、codedWidth 等元数据,配合 requestVideoFrameCallback 实现帧级同步。工程价值:帧级裁剪、滤镜、转码预览可在浏览器内低延迟完成;配置 hardwareAcceleration 可启用硬件加速降低 CPU 占用;帧用完需调用 close() 释放底层资源。与 MSE 相比,MSE 面向播放器消费媒体流,解码时机由浏览器掌控;WebCodecs 面向逐帧处理管线,开发者全权管理,更适合编辑与处理而非纯播放。

考察 WebCodecs 在编辑管线的定位与价值:VideoDecoder 直接产出 VideoFrame,应用可控输入输出队列与时间戳,延迟低且可逐帧处理;VideoFrame 可零拷贝绘制到 Canvas 或送入编码器,相比 MSE 的播放器管道更适合编辑、转码与实时处理等场景,这也是媒体处理应用选型时的重要考量。

const decoder = new VideoDecoder({
  output: (frame) => { ctx.drawImage(frame, 0, 0); frame.close(); },
  error: console.error,
});
await decoder.configure({ codec: 'avc1.42E01E', codedWidth: 1920, codedHeight: 1080 });
decoder.decode(new EncodedVideoChunk({ type: 'key', timestamp: 0, data }));
#
★★★

23. File API、Blob、File、FileReader、URL.createObjectURL 的协作

Blob、File、FileReader 与 URL.createObjectURL 分别承担什么职责?如何协作处理文件上传与预览?

  • Blob 是不可变二进制数据容器,File 是其子类
  • FileReader 异步读取文本、DataURL 与 ArrayBuffer
  • createObjectURL 生成内存 URL 用于零拷贝预览与下载

Blob 是浏览器内不可变的二进制数据容器,File 继承 Blob 并附加 name、lastModified、type 等文件元数据,二者都支持 slice() 切片、stream() 流式读取与 arrayBuffer() 读取。FileReader 提供异步读取接口:readAsText 读文本、readAsDataURL 生成 base64、readAsArrayBuffer 读二进制,适合小文件与需要统一编码的场景。URL.createObjectURL(blob) 在内存中为 Blob 生成 blob: URL,可直接作为 文章配图/ 的 src 或 的 download 目标,零拷贝、性能好,是预览与下载的主流方式,但会占用内存,用完后必须 URL.revokeObjectURL 释放。上传时直接拿 File 构造 FormData 提交;大文件可用 File.slice 分片并配合流式读取。协作要点:预览走 createObjectURL,需要文本/二进制内容走 FileReader 或 arrayBuffer,上传走 FormData,按场景选择而非混用。

考察文件处理 API 的分工与协作:Blob 与 File 是数据载体,FileReader 用于异步读取文本、DataURL 与二进制内容,createObjectURL 生成零拷贝的预览与下载地址;理解各 API 的适用场景并及时 revoke 释放内存,是避免内存泄漏与界面卡顿的关键实践,也是上传预览功能稳定运行的保障。

const url = URL.createObjectURL(file);
img.src = url;
img.onload = () => URL.revokeObjectURL(url);
#
★★★

24. File System Access API 与 Origin Private File System(OPFS)

File System Access API 与 OPFS 有什么区别?OPFS 为什么适合高性能存储?

  • showOpenFilePicker/showSaveFilePicker 访问用户文件需授权
  • OPFS 是源私有的高性能存储,无需用户授权
  • createSyncAccessHandle 同步访问与 Worker 配合

File System Access API 让网页读写用户可见的文件系统:通过 showOpenFilePicker/showSaveFilePicker 获取 FileSystemFileHandle,读写前需用户授权,句柄可持久化(IndexedDB 存储句柄)以恢复权限,适合文件导入导出与桌面级应用。OPFS(Origin Private File System)是浏览器为每个源提供的私有虚拟文件系统,在配额内无需用户授权,数据不暴露到用户可见目录,适合缓存、离线数据与数据库备份。OPFS 的性能优势:提供 createSyncAccessHandle() 同步读写接口(仅限 Worker 使用),消除异步开销;支持大文件随机访问与 WritableStream 流式写入;配合 move()/remove() 可做文件管理。工程取舍:用户可见文件用 File System Access API,应用私有数据用 OPFS;在 Worker 中用同步句柄可获得接近本地磁盘的吞吐。

考察两类文件系统 API 的边界:File System Access API 面向用户可见文件且需授权,OPFS 是源私有的高性能存储无需授权;OPFS 的同步访问句柄可在 Worker 中提供接近本地磁盘的吞吐;掌握选型与数据放置策略,是文件类应用架构设计与性能优化的基础。

const root = await navigator.storage.getDirectory();
const handle = await root.getFileHandle('data.bin', { create: true });
const sync = await handle.createSyncAccessHandle();
sync.write(new Uint8Array([1, 2, 3]), { at: 0 });
sync.close();
#
★★★

25. CompressionStream 与 DecompressionStream 在浏览器端 gzip/deflate 流式处理的实际收益

CompressionStream 与 DecompressionStream 支持哪些压缩格式?在浏览器端流式处理有什么实际收益?

  • 原生支持 gzip、deflate、deflate-raw 格式
  • 基于 TransformStream 的流式处理
  • 减少传输体积、内存占用与网络耗时

CompressionStream 与 DecompressionStream 是浏览器原生实现的 TransformStream,支持 gzip、deflate、deflate-raw 三种格式,可直接在流式管道中压缩或解压数据。实际收益:一是内存友好,边读边压缩,无需把大文件整体读入内存或先转 base64 再压缩,适合大 JSON、日志、CSV 的传输与存储;二是减少网络体积,客户端先压缩再上传、下载时解压,可显著降低带宽与耗时,gzip 压缩 JSON 通常可达数倍压缩率;三是与 fetch、Blob.stream()、FileSystemWritableFileStream 天然衔接,例如用 response.body.pipeThrough(new DecompressionStream('gzip')) 解压流式响应。注意:HTTP 的 Content-Encoding 由浏览器自动处理,此 API 用于应用层自定义压缩;解压失败会终止管道,需处理错误;Web Worker 中同样可用。

考察浏览器原生压缩 API 的流式特性:CompressionStream 基于 TransformStream 支持 gzip 与 deflate,边读边处理避免整块内存占用,可显著降低传输体积;它用于应用层自定义压缩而非替代 HTTP 的 Content-Encoding,解压失败会终止管道,需做好错误处理与降级,保证功能在异常场景下仍可用。

const stream = blob.stream()
  .pipeThrough(new CompressionStream('gzip'));
const gzipped = await new Response(stream).blob();
#
★★★

26. Media Session API(navigator.mediaSession)如何向系统媒体通知、锁屏与耳机按键暴露播放元数据与 play/pause/seek/nexttrack 动作,多页面同时播放时由谁决定动作归属?

navigator.mediaSession 如何向系统媒体通知、锁屏与耳机按键暴露播放控制?多页面同时播放时动作归属由谁决定?

  • mediaSession.metadata 与 artwork 的元数据暴露
  • setActionHandler 注册 play/pause/seek/nexttrack 动作
  • 多页面播放时以最近活跃的媒体会话为动作归属

navigator.mediaSession 让页面把播放元数据与控制动作暴露给系统:设置 metadata 后,系统媒体通知、锁屏与通知栏会展示标题、艺术家、专辑与封面(artwork);通过 setActionHandler 注册 play、pause、seekto、seekbackward、seekforward、previoustrack、nexttrack 等动作,耳机按键与系统 UI 点击会触发对应回调,实现页面在后台时仍可被系统控制。多页面同时播放时,浏览器以"最近一次获得媒体焦点"的会话为归属:通常是最后调用 play() 或最近被用户聚焦的标签页,系统控制作用于该会话,新播放会抢占媒体焦点并使先前页面的播放状态变为暂停。工程上需在播放、暂停、切歌与销毁时同步更新 metadata、playbackState 与 action 状态,避免系统 UI 显示过期信息。

考察媒体会话与宿主系统的集成:metadata 与 artwork 向系统通知与锁屏暴露播放信息,setActionHandler 注册的 play、seek、nexttrack 等动作由系统 UI 与耳机按键触发;多页面同时播放时以最近活跃的媒体会话为归属,需同步 playbackState 避免系统界面显示过期状态。

navigator.mediaSession.metadata = new MediaMetadata({
  title: '歌曲名', artist: '歌手', album: '专辑',
  artwork: [{ src: coverUrl, sizes: '512x512', type: 'image/png' }],
});
navigator.mediaSession.setActionHandler('play', () => audio.play());
navigator.mediaSession.setActionHandler('nexttrack', () => next());
navigator.mediaSession.playbackState = 'playing';
#
★★★

27. WebCodecs 的 VideoEncoder/VideoDecoder 在低延迟视频编辑中的配置(hardwareAcceleration、latencyMode)与性能调优

WebCodecs 的 VideoEncoder/VideoDecoder 有哪些关键配置?hardwareAcceleration 与 latencyMode 如何影响性能?

  • configure 中的 codec、分辨率、码率等参数
  • hardwareAcceleration 的硬件/软件编解码选择
  • latencyMode 在画质与延迟之间的权衡

VideoEncoder/VideoDecoder 通过 configure() 配置:VideoEncoder 需指定 codec、width、height、bitrate、framerate,以及 bitrateMode(constant/variable/quantizer)与 latencyMode;VideoDecoder 需指定 codec、codedWidth/codedHeight 与 hardwareAcceleration。hardwareAcceleration 取值 no-preference、prefer-hardware、prefer-software:硬件加速省电且降低 CPU 占用,但不同设备能力差异大,软件路径兼容性最好,生产环境宜做能力检测与回退。latencyMode 取值 quality 或 realtime:quality 让编解码器追求更高压缩效率与画质,适合转码与离线处理;realtime 优先低延迟,限制缓冲与 B 帧,适合直播与实时通话。调优要点:按分辨率与帧率匹配码率;监听 dequeue 事件按需喂帧避免队列积压;解码帧用完调用 close() 释放底层资源,防止内存泄漏。

考察 WebCodecs 配置项的性能语义:hardwareAcceleration 决定硬件或软件编解码路径,latencyMode 在画质与延迟之间权衡,码率与分辨率需匹配业务场景;配合 dequeue 事件按需喂帧与帧资源 close 释放,才能构建低延迟且稳定的编解码管线,并处理兼容性回退。

const encoder = new VideoEncoder({
  output: (chunk) => send(chunk),
  error: console.error,
});
await encoder.configure({
  codec: 'avc1.42E01E', width: 1280, height: 720,
  bitrate: 2_000_000, framerate: 30,
  hardwareAcceleration: 'prefer-hardware',
  latencyMode: 'realtime',
});
encoder.addEventListener('dequeue', () => { /* 按需喂帧 */ });
#
★★★

28. EyeDropper API 在主题色提取的浏览器兼容与权限策略

EyeDropper API 的用途是什么?它的浏览器兼容性与权限策略如何?

  • EyeDropper.open() 拾取屏幕颜色并返回 sRGBHex
  • 需要用户手势与安全上下文
  • 浏览器支持现状与降级方案

EyeDropper API 允许 Web 应用调用系统级取色器,用户从屏幕任意位置拾取颜色,返回 sRGBHex 格式(如 #1a2b3c)。用法:new EyeDropper().open() 必须在用户手势(点击)中调用,返回 Promise,用户选取后 resolve 颜色,取消则 reject AbortError;拾取期间系统取色界面接管屏幕,页面交互被暂时屏蔽。权限策略:无需显式权限请求,但要求安全上下文(HTTPS)且必须有用户激活手势,防止应用无感触发取色;页面失去焦点或用户按 Esc 会中止。兼容性:Chrome/Edge 桌面端支持,Firefox 与 Safari 尚未实现,移动端基本不支持。生产环境应做特性检测('EyeDropper' in window),不支持时降级为隐藏的 或自研色板,保证主题色提取功能可用。

考察 EyeDropper 的使用与权限边界:open 方法必须在用户手势中调用并返回 sRGBHex 颜色,无显式权限但要求安全上下文;该 API 主要支持 Chromium 桌面端,Firefox 与 Safari 缺失,生产环境应特性检测并降级为颜色输入控件或自研色板,保证主题色提取功能可用。

if ('EyeDropper' in window) {
  const result = await new EyeDropper().open();
  color = result.sRGBHex;
}
#
★★★

29. Popover API 在多语言/多模态的协作边界

Popover API 提供什么能力?在多语言文本与多模态(键盘、触摸、辅助技术)协作上有哪些边界?

  • popover 属性与 showPopover/hidePopover 控制
  • 顶层图层渲染与 light-dismiss 行为
  • RTL、焦点管理、Esc 与无障碍语义的边界

Popover API 为任意元素提供原生弹层能力:元素声明 popover 属性即成为 popover,用 showPopover()/hidePopover()/togglePopover() 控制显隐;弹层渲染在顶层图层(top layer),不受 overflow 与 transform 裁剪;默认支持 light-dismiss,点击外部或按 Esc 自动关闭。多语言与多模态边界:文本方向需配合 dir 属性处理 RTL 语言;键盘交互依赖 Esc 关闭,但 API 不会自动把焦点移入弹层,复杂弹层需自行实现焦点陷阱;触摸设备上 light-dismiss 依赖全局点击,滚动与拖拽操作需避免误关;辅助技术上应显式声明 role="dialog" 等语义并管理 aria-expanded 与焦点状态,确保屏幕阅读器能感知显隐变化。相比手写遮罩层,Popover API 省去 z-index 与定位维护,但这些边界仍需应用层补齐。

考察 Popover API 的能力与多模态边界:弹层渲染在顶层图层并支持点击外部与 Esc 关闭,省去 z-index 与定位维护;但焦点不会自动移入弹层、RTL 文本方向与辅助技术语义需应用层补齐,复杂弹层要自行实现焦点陷阱并管理 aria 状态,才能保证可用性。

<button popovertarget="menu">打开</button>
<div id="menu" popover role="dialog" aria-label="菜单">
  菜单内容
</div>
#
★★★

30. 与 srcset 在响应式图片中的取舍

与 srcset/sizes 分别解决什么问题?在响应式图片中应如何取舍与组合?

  • srcset 按视口尺寸与像素密度选图
  • 按媒体条件与格式切换源
  • 二者组合使用与 文章配图 兜底

srcset 与 sizes 解决"同一内容、不同尺寸与密度"的选择:srcset 列出候选图及其宽度描述符(如 480w、960w)或密度描述符(1x、2x),sizes 告诉浏览器图片的实际布局宽度,浏览器按视口、DPR 与网络状况挑选最合适的源,兼顾画质与流量。而 解决"不同场景用不同文件"的问题: 按媒体查询切换横竖版、裁切或艺术方向(art direction), 按格式支持切换 AVIF/WebP/JPEG。取舍原则:仅需按尺寸或密度缩放时用 srcset+sizes,浏览器可自动优化;需要按断点换图或格式降级时用 ,且其中的 文章配图 仍可嵌套 srcset。二者可组合使用,并始终保留 文章配图 作为兜底,保证无样式或格式不支持时仍能显示图片。

考察响应式图片两套机制的差异与组合:srcset 与 sizes 按视口尺寸和像素密度选图,picture 按媒体条件与格式切换源;仅需缩放时用 srcset,需要艺术方向或格式降级时用 picture,其中 img 可再嵌套 srcset,并始终保留 img 作为无样式兜底,兼顾画质与流量成本。

<picture>
  <source type="image/avif" srcset="hero.avif">
  <source media="(max-width: 600px)" srcset="hero-mobile.webp">
  <img src="hero.jpg" srcset="hero-2x.jpg 2x" sizes="(max-width: 600px) 100vw, 50vw" alt="横幅">
</picture>
#
★★★

31. WebGPU 的 Compute Shader 在浏览器端物理模拟与图像处理的工程价值

WebGPU 的 Compute Shader 在浏览器端物理模拟与图像处理中有什么工程价值?存在哪些边界?

  • Compute Shader 以工作组并行执行通用计算
  • 计算与渲染共享纹理,减少 CPU-GPU 拷贝
  • 兼容性、调试与内存管理边界

WebGPU 提供通用计算能力,Compute Shader(WGSL)在 GPU 上以工作组(workgroup)并行执行,适合物理模拟(粒子、刚体、流体)、图像处理(卷积、滤波、颜色分级)与数据处理等大规模并行任务。工程价值:一是把计算从 CPU/JS 卸载到 GPU,单帧可处理数十万粒子而不阻塞主线程;二是计算与渲染共享同一设备与纹理,计算结果可直接作为采样纹理用于渲染,避免 CPU-GPU 往返拷贝;三是通过 storage buffer 与 workgroup 共享内存高效交换数据,支持更大的数据规模。边界:需要支持 WebGPU 的浏览器与硬件,必须处理适配器/设备获取失败的回退;WGSL 调试工具较少,着色器错误排查成本高;buffer 生命周期与映射管理需谨慎,避免内存峰值。

考察 WebGPU 计算管线的工程价值:Compute Shader 以工作组并行执行通用计算,适合粒子、流体与图像处理等大规模任务;计算与渲染共享纹理可减少 CPU-GPU 拷贝,但需处理浏览器与硬件兼容、设备获取失败回退,以及 WGSL 调试与内存生命周期的管理。

const pass = encoder.beginComputePass();
pass.setPipeline(computePipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(64, 64, 1);
pass.end();
#
★★★

32. requestVideoFrameCallback 在视频同步与抽帧分析的能力

requestVideoFrameCallback 提供什么能力?在视频同步与抽帧分析中如何应用?

  • 每帧回调携带 mediaTime、presentedFrames 等元数据
  • 与视频帧一一对应的帧级同步
  • 抽帧绘制、分析与卡顿统计

requestVideoFrameCallback 让应用在视频每一帧被合成显示前收到回调,回调参数包含 mediaTime、presentedFrames、expectedDisplayTime 等元数据,可实现与视频帧精确同步的逻辑。相比 requestAnimationFrame,rAF 按显示刷新率触发,无法知道视频当前播放到哪一帧;rVFC 与视频帧一一对应,可精确实现字幕同步、歌词高亮、逐帧特效与帧率统计。抽帧分析场景:在回调中把当前帧绘制到 Canvas,配合 OffscreenCanvas 与 Worker 做图像分析(人脸检测、二维码识别、画面内容审核);也可通过 presentedFrames 与 mediaTime 的差值判断丢帧与卡顿。注意:回调运行在视频渲染流水线上,逻辑应保持轻量,重活应转移到 Worker;页面不可见时回调会停止触发,需处理后台暂停。

考察帧级同步回调的用途与实现:rVFC 与视频帧一一对应并携带 mediaTime、presentedFrames 等元数据,可实现字幕同步、逐帧特效与丢帧统计;回调运行在渲染流水线上,逻辑应轻量并配合 OffscreenCanvas 与 Worker 做重分析,页面不可见时回调停止触发。

function onFrame(now, meta) {
  ctx.drawImage(video, 0, 0, w, h);
  if (meta.mediaTime - lastTime > 1 / 30) dropped++; // 丢帧统计
  video.requestVideoFrameCallback(onFrame);
}
video.requestVideoFrameCallback(onFrame);
#
★★★

33. Worker、SharedWorker、Service Worker 的线程模型与作用域

Worker、SharedWorker、Service Worker 在线程模型与作用域上有什么区别?如何选型?

  • 专用 Worker 页面级一对一通信
  • SharedWorker 多标签页共享的广播式作用域
  • Service Worker 独立于页面的网络代理生命周期

三者同属 Web Worker 体系,但线程模型与作用域不同。专用 Worker:每个页面实例创建独立线程,与创建它的页面一对一,通过 postMessage/onmessage 通信,生命周期随页面,适合计算密集任务的卸载。SharedWorker:单一线程可被同源多个标签页共享,通过 MessagePort 与各页面连接,适合跨标签共享状态、复用 WebSocket 连接,但生命周期由所有连接共同维持,最后一个连接关闭即销毁。Service Worker:独立于页面运行,不直接面向页面通信,充当网络代理,生命周期由 install/activate/fetch 事件驱动,支持离线缓存、推送与后台同步;作用域由注册路径决定,可控制该路径下所有页面,但事件是共享的。选型:单页任务用专用 Worker,跨标签共享用 SharedWorker,离线与网络拦截用 Service Worker。

考察三类 Worker 的线程模型与作用域差异:专用 Worker 与页面一对一,SharedWorker 被同源多标签页共享并通过端口通信,Service Worker 是独立生命周期的事件驱动网络代理;理解作用域与生命周期才能按任务卸载、跨页共享与离线拦截等场景正确选型。

// 专用 Worker
const worker = new Worker('task.js');
worker.postMessage({ type: 'compute', data });
#
★★

34. VideoFrame 与 createImageBitmap 在视频帧截图的性能对比

VideoFrame 与 createImageBitmap 在视频帧截图场景有什么性能差异?应如何选择?

  • createImageBitmap 从视频解码并复制生成位图
  • VideoFrame 零拷贝引用视频帧数据
  • 高频抽帧与低频截图的选择策略

视频帧截图可用两种方式:createImageBitmap(video) 会从当前视频帧解码并复制生成独立位图,返回的 ImageBitmap 可直接绘制到 Canvas 或上传纹理,内存由开发者掌控;VideoFrame 是 WebCodecs 解码或视频元素产出的帧引用,携带 timestamp、duration 等元数据,绘制到 Canvas 时直接使用底层帧数据,零拷贝、开销更低。性能对比:VideoFrame 避免了像素复制与格式转换,适合高频抽帧(逐帧预览、批量缩略图、实时分析);createImageBitmap 生成可复用的独立位图,适合低频截图与需要长期持有的图像。工程要点:VideoFrame 使用后必须调用 close() 释放底层资源,否则造成内存泄漏;createImageBitmap 用完同样需要 close();高频场景优先 VideoFrame + OffscreenCanvas,低频场景用 createImageBitmap 更简单直接。

考察视频帧截图的性能差异与选择:VideoFrame 零拷贝引用底层帧数据、开销低但用后必须 close,适合高频抽帧与逐帧处理;createImageBitmap 复制生成独立位图适合低频截图与长期持有;按频率与生命周期选择并管理资源,是避免内存泄漏的关键。

#
★★

35. WebXR 在沉浸式页面中的渲染管线

WebXR 的渲染管线是怎样的?与普通 WebGL/WebGPU 渲染有什么不同?

  • XRSession 与 XR 帧循环的驱动方式
  • 左右眼视图矩阵与投影矩阵
  • 渲染到会话 framebuffer 由浏览器合成

WebXR 的渲染管线围绕 XRSession 展开:应用先请求 XRSession(immersive-vr、immersive-ar 或 inline),通过 requestReferenceSpace 获取参考空间,然后循环调用 session.requestAnimationFrame 获得 XRFrame;每帧通过 viewer pose 与视图数组(左右眼各一个 view)获取 viewport、view 矩阵与 projection 矩阵,把场景分别渲染到两个视图对应的 GPU 纹理(会话 framebuffer),最终由浏览器合成到头显或屏幕。与普通 WebGL 的关键差异:一是必须按视图数组为每只眼睛单独渲染,使用 XR 提供的投影矩阵保证立体正确;二是帧循环由 XR 设备刷新驱动,不能使用自己的 rAF;三是渲染目标绑定 session 的 framebuffer 而非页面 canvas;四是 AR 场景需用 XRWebGLBinding 合成相机纹理与平面检测结果。

考察 WebXR 渲染管线的特殊性:XR 帧循环由会话驱动而非页面 rAF,每帧需按左右眼视图数组分别渲染,结果绘制到会话 framebuffer 由浏览器合成;理解视图矩阵、投影矩阵与参考空间的关系,是正确实现立体沉浸式渲染的基础。

function onXRFrame(time, frame) {
  const pose = frame.getViewerPose(refSpace);
  for (const view of pose.views) {
    // 用 view.projectionMatrix / view.transform 渲染该眼视图
  }
  session.requestAnimationFrame(onXRFrame);
}
#
★★

36. ImageDecoder API 在逐帧 GIF/WebP 动画解码中的工程应用与 createImageBitmap 的性能对比

ImageDecoder API 如何逐帧解码 GIF/WebP 动画?与 createImageBitmap 相比性能如何?

  • ImageDecoder 按帧索引解码与随机访问
  • 帧元数据与轨道信息
  • 与 createImageBitmap 一次性解码的对比

ImageDecoder 是 WebCodecs 家族的图像解码 API,可解码 GIF、WebP、JPEG、PNG 等格式:new ImageDecoder({ data }) 后通过 tracks.selectedTrack 获取帧数、时长等信息,用 decode({ frameIndex }) 按帧索引解码,支持随机访问任意帧与按时间 seek,适合动画逐帧播放、缩略图预览与帧编辑。相比 createImageBitmap:createImageBitmap 一次解码整张图片,动画默认只取第一帧(或需传帧参数),解码完成后一次性给到位图;ImageDecoder 可延迟到需要时才解码指定帧,内存占用更可控,缓存与解码策略由开发者掌控,适合长 GIF 与大批量图片处理。工程要点:用 complete 等待元数据就绪;解码出的 ImageBitmap 用完调用 close();逐帧播放时预取前后帧避免卡顿。

考察图像解码 API 的差异:ImageDecoder 支持按帧索引解码与随机访问,可按需解码控制内存,适合 GIF 与 WebP 动画的逐帧播放与编辑;createImageBitmap 一次性解码整图适合单帧场景;理解解码时机与缓存策略才能避免动画播放卡顿,保证动画播放体验流畅。

const decoder = new ImageDecoder({ data: buffer });
await decoder.complete;
const { image } = await decoder.decode({ frameIndex: 0 });
ctx.drawImage(image, 0, 0);
image.close();
#
★★

37. 视频 的 playsinline、muted、autoplay 在 iOS Safari 的策略

iOS Safari 对 的 autoplay、muted、playsinline 有什么策略?如何实现自动播放?

  • autoplay 需 muted 才能无手势自动播放
  • playsinline 控制是否内联播放
  • 有声播放必须由用户手势触发

iOS Safari 对视频自动播放有严格限制:静音视频(muted)允许在页面加载后自动开始播放,而带声音的视频必须经过用户手势(点击、触摸)才能开始;playsinline 属性让视频在页面内联播放,避免默认进入全屏播放器。要实现首屏自动播放,通常组合为 autoplay muted playsinline loop,并设置 preload="auto",此时静音视频可自动循环,适合背景视频;若需要声音,应监听首次触摸/点击事件后调用 video.play() 并恢复音量。注意:动态设置 muted 后再 play() 在部分版本中仍可能被拦截,应在播放前完成 muted 配置;非 muted 视频还要求页面活跃且获得用户激活。工程上应用 Promise 捕获 play() 被拒绝的情况,提供显式播放按钮兜底。

考察 iOS 平台视频自动播放策略:静音视频配合 playsinline 可无手势自动播放,有声播放必须由用户交互触发;实现背景视频常用 autoplay muted playsinline loop 组合,并捕获 play 被拒绝的情况提供播放按钮兜底,避免自动播放功能在移动端失效。

<video autoplay muted playsinline loop preload="auto">
  <source src="bg.mp4" type="video/mp4">
</video>
#
★★

38. WebCodecs API 在低延迟视频解码与视频编辑场景相较于 MSE 的取舍

WebCodecs API 在低延迟视频解码与视频编辑场景相较于 MSE 有什么取舍?

  • WebCodecs 应用直接控制解码输入输出与帧生命周期
  • MSE 面向播放器的缓冲与解码管道
  • 灵活性与复杂度的权衡

WebCodecs 让应用直接驱动编解码器:decode() 输入 EncodedVideoChunk,通过 output 回调获得 VideoFrame,应用全权管理队列、时间戳与帧生命周期,绕开 MSE 的 SourceBuffer 缓冲与浏览器媒体管道,端到端延迟更低,适合低延迟直播、视频编辑与逐帧处理。MSE 面向播放器消费媒体流:通过 appendBuffer 向 SourceBuffer 喂数据,浏览器负责缓冲、解码与播放同步,接口简单、兼容性好,适合播放器场景,但解码时机与缓冲策略由浏览器掌控,延迟与精细控制受限。取舍:WebCodecs 灵活性高、延迟低,可与 Canvas/WebGL 深度集成,但需要自行实现容器解析(如 MP4 demux)、音视频缓冲与同步,复杂度高;MSE 开箱即用但控制粒度粗。生产环境常在播放器用 MSE,在编辑器与低延迟链路用 WebCodecs。

考察两类媒体 API 的取舍:WebCodecs 让应用直接控制解码队列与帧生命周期,延迟低且适合逐帧编辑,但需自行实现容器解析与音视频同步;MSE 面向播放器开箱即用、兼容性好但控制粒度粗;应按播放与处理场景选择,并评估相应的维护成本。

#
★★

39. createImageBitmap() 在 Worker 中异步解码大图与内存管理的工程实践

createImageBitmap() 在 Worker 中如何异步解码大图?内存管理有哪些工程实践?

  • Worker 中解码避免阻塞主线程
  • resize 参数控制解码产物尺寸与内存
  • close() 释放与并发限流

createImageBitmap(blob, options) 可在任意线程异步解码图片,返回 Promise;把解码放到 Worker,主线程只接收绘制对象,避免大图解码阻塞 UI。工程实践:一是解码前用 options 控制产物,resizeWidth/resizeHeight 让浏览器按需缩放,避免解码超大图占用巨量内存,imageOrientation 与 colorSpaceConversion 控制方向与色彩转换;二是配合 fetch 获取 ArrayBuffer/Blob 后直接解码,无需先创建 文章配图;三是内存管理:ImageBitmap 用完必须 close() 释放 GPU 内存,Worker 中批量解码后要及时关闭不再使用的位图,避免内存峰值;四是按需解码与懒加载结合,配合 IntersectionObserver 在图片临近视口时才解码。注意解码并发过高会争抢内存,应做队列限流与优先级控制。

考察 Worker 中图片解码的工程实践:在 Worker 中调用 createImageBitmap 可避免大图解码阻塞主线程,resize 参数按需缩放控制内存,位图用完必须 close 释放;配合懒加载与并发限流管理解码队列,才能在高吞吐场景下保持内存稳定与应用流畅。

// worker.js
self.onmessage = async ({ data }) => {
  const bmp = await createImageBitmap(data.blob, { resizeWidth: 800 });
  self.postMessage(bmp, [bmp]); // 转移位图
};
#
★★

40. 3D 模型格式(glTF、USDZ)在浏览器端加载与渲染的优化路径

glTF 与 USDZ 在浏览器端加载与渲染各有什么特点?有哪些优化路径?

  • glTF 是 Web 3D 标准格式,支持 PBR 与动画
  • USDZ 面向 iOS AR Quick Look 原生预览
  • 二进制、几何压缩、KTX2 纹理与 LOD 优化

glTF 是 Web 3D 的开放标准格式,采用 JSON 加二进制结构,支持 PBR 材质、骨骼动画、场景图与扩展机制,three.js、Babylon.js 等引擎原生支持,适合浏览器内交互渲染。USDZ 是 Apple 推动的 AR 查看格式(基于 USD),iOS 系统通过 AR Quick Look 原生预览,无需自建渲染管线,但浏览器内自定义交互受限,主要用于电商 AR 与 iOS 场景。优化路径:使用 .glb 二进制容器减少解析开销;用 Draco 或 Meshopt 压缩几何体减小体积;纹理用 KTX2/Basis 压缩并支持 GPU 直接解码;拆分 LOD 与实例化减少绘制调用;懒加载与预加载结合,大型场景渐进式流式加载;材质与动画按需激活。选型按目标平台与交互需求:需要 Web 内深度交互用 glTF,iOS 快速 AR 预览用 USDZ。

考察 3D 格式选型与加载优化:glTF 是 Web 标准格式,支持 PBR 材质与动画,可在浏览器内深度交互;USDZ 面向 iOS AR Quick Look 原生预览;优化重点是二进制容器、几何压缩、KTX2 纹理与 LOD 分级,按目标平台与交互需求选择合适格式,平衡加载速度与视觉效果。

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load('model.glb', (gltf) => scene.add(gltf.scene));
#
★★

41. WebVTT 字幕轨在视频播放器的同步与样式自定义工程实现

WebVTT 字幕轨如何与视频同步?样式自定义有哪些工程实现方式?

  • 与 textTrack 的 cue 时间同步机制
  • ::cue 伪元素定制字幕样式
  • 复杂样式需监听 cuechange 自行渲染

WebVTT 通过 元素把字幕轨挂到 上,浏览器解析 .vtt 文件中的 cue(含起止时间与文本),在播放过程中自动按时间激活 cue 并叠加显示,无需手动同步。工程实现:一是用 textTrack.mode 控制显示状态(disabled/hidden/showing),监听 cuechange 事件可拿到当前激活的 cue 做业务处理(高亮、翻译、统计);二是样式定制用 ::cue 伪元素设置字体、颜色、背景、位置与文本阴影,但支持的 CSS 属性有限;三是需要复杂样式(弹幕、逐字高亮、多行排版)时,可把 mode 设为 hidden,自行监听 cuechange 用 DOM 渲染;四是注意 VTT 时间戳格式、cue 设置(position/align/line)与中文字幕编码,跨浏览器需做兼容测试。

考察字幕轨的工程实现路径:track 元素由浏览器按 cue 时间自动同步激活字幕,cuechange 事件可获取当前 cue 做业务处理;样式定制用 cue 伪元素但属性有限,复杂样式需自行渲染;同时注意 VTT 时间戳格式、中文编码与跨浏览器差异。

<video controls src="movie.mp4">
  <track kind="subtitles" src="subs.vtt" srclang="zh" label="中文" default>
</video>
video.textTracks[0].addEventListener('cuechange', () => {
  const cue = video.textTracks[0].activeCues?.[0];
  if (cue) highlight(cue.text);
});
#
★★

42. 在视频章节导航与 SEO 的工程价值

如何实现视频章节导航?在 SEO 上有什么价值?
  • chapters 轨道用 cue 定义章节标题与时间
  • 播放器据此生成章节导航菜单
  • 结构化章节元数据对 SEO 与无障碍的价值
用 WebVTT 定义视频章节:每个 cue 的起止时间对应一个章节,文本即章节标题,浏览器与播放器可据此生成章节导航菜单,用户点击即可跳转到对应时间点。工程价值:一是提升长视频的可导航性,用户快速定位感兴趣片段,减少跳出;二是章节标题与时间戳构成结构化内容元数据,帮助搜索引擎理解视频结构,视频可能获得章节富媒体展示,提升点击率与 SEO 表现;三是配合无障碍,章节列表让键盘与读屏用户更快导航。实现要点:使用 vtt 文件并声明 kind="chapters"、label 与 srclang;播放器侧读取 textTrack 的 cues 构建菜单,点击时设置 currentTime;章节标题应简洁、关键词明确;注意与字幕轨区分命名。

考察章节轨道的工程价值:chapters 轨道用 cue 定义章节标题与时间区间,播放器据此生成导航菜单支持跳转;结构化章节元数据帮助搜索引擎理解视频内容,可能获得富媒体展示并提升 SEO;实现时需正确声明 kind、label 与 srclang 并处理 cues 读取。

<video controls src="talk.mp4">
  <track kind="chapters" src="chapters.vtt" label="章节" default>
</video>
const cues = video.textTracks[0].cues;
Array.from(cues).forEach((c) => {
  menu.append(makeItem(c.text, () => { video.currentTime = c.startTime; }));
});
#
★★

43. 图像懒加载与解码优先级提示(fetchpriority="high")在 LCP 优化的协同

loading="lazy" 与 fetchpriority="high" 如何协同优化 LCP?有哪些注意事项?

  • loading="lazy" 延迟视口外图片加载
  • fetchpriority="high" 提高首屏关键图优先级
  • 配合 preload 与防 CLS

loading="lazy" 让视口外的图片延迟到接近视口时才加载,减少首屏带宽与请求竞争;fetchpriority="high" 提示浏览器提高首屏关键图片(LCP 元素)的加载与解码优先级。二者协同:首屏 LCP 图片应尽早被发现并高优先级加载——最佳实践是 LCP 图片不使用 lazy,加 fetchpriority="high" 并配合 提前启动下载;视口外图片统一 lazy 并设 fetchpriority="low" 或 auto,避免与关键资源竞争。注意事项:lazy 图片要设置宽高或 aspect-ratio 防止布局偏移(CLS);fetchpriority 只是提示,最终由浏览器综合决策;预加载不能滥用,否则挤占带宽。核心目标是让 LCP 资源"早加载、高优先级、不引起布局抖动"。

考察加载策略与优先级提示的协同:LCP 图片不应懒加载,应加 fetchpriority high 并配合 preload 提前下载;视口外图片用 lazy 并降低优先级避免竞争;同时为图片设置宽高防止布局偏移,理解优先级只是提示而非强制的加载顺序,才能正确优化核心指标。

<link rel="preload" as="image" href="hero.webp">
<img src="hero.webp" fetchpriority="high" width="1200" height="630" alt="主视觉">
<img src="ad.webp" loading="lazy" fetchpriority="low" alt="广告">
#
★★

44. WebXR 在浏览器端 VR/AR 沉浸式体验的现状、API 兼容与降级方案

WebXR 在浏览器端 VR/AR 的现状如何?API 兼容性如何?有哪些降级方案?

  • WebXR 的沉浸式会话类型与能力
  • 浏览器与设备兼容现状
  • 特性检测与 immersive→inline→2D 降级

WebXR Device API 是浏览器端 VR/AR 的标准入口,支持 immersive-vr、immersive-ar 与 inline 会话,可访问头显/AR 设备的位姿、控制器与平面检测;Meta Quest 浏览器、Chrome Android 的 ARCore 设备、部分 PC 头显浏览器支持较好,iOS Safari 长期不支持 WebXR,只能走原生 ARKit 或第三方方案。兼容性要点:必须做特性检测(navigator.xr 存在且 await navigator.xr.isSessionSupported('immersive-vr') 为 true)再进入,不能假设可用;会话需要用户手势与安全上下文。降级方案:不支持时回退 inline 会话实现"伪 3D"(如 CSS 3D 或 Three.js 透视投影);AR 场景降级为相机预览加 2D 覆盖(getUserMedia);再降级为普通 2D 页面。工程上应把核心内容做成渐进增强,XR 只是增强层。

考察 WebXR 的兼容现状与降级策略:主流安卓与头显浏览器支持沉浸式会话,iOS Safari 长期缺失;使用前必须特性检测并请求用户手势,不支持时按沉浸式、伪 3D、2D 逐级降级,把 XR 作为渐进增强层,保证核心内容在所有端可用。

if (navigator.xr && await navigator.xr.isSessionSupported('immersive-vr')) {
  const session = await navigator.xr.requestSession('immersive-vr');
} else {
  fallbackToInline(); // 降级方案
}
#
★★

45. 视频预加载 preload="metadata" 与 preload="auto" 在流量与首屏的取舍

preload="metadata" 与 preload="auto" 有什么区别?在流量与首屏体验上如何取舍?

  • metadata 只加载元数据与首帧信息
  • auto 允许浏览器预加载媒体数据
  • 流量成本与播放体验的平衡

preload 属性告诉浏览器视频的预加载策略。preload="metadata" 只加载时长、尺寸、首帧等元数据,不下载媒体数据,流量消耗极小,适合视频列表页或用户可能不看的场景,点击播放时再从当前点流式加载;preload="auto" 允许浏览器在空闲带宽下预加载尽可能多的数据甚至整段视频,点击播放即刻开始,首帧与缓冲体验最好,但会消耗大量流量。取舍原则:列表页与非主内容视频用 metadata;首屏主视频或用户即将观看的内容用 auto;移动网络下优先 metadata 并按需加载;可结合 IntersectionObserver 在视频接近视口时动态把 preload 提升为 auto;用 poster 提供首帧占位,避免 metadata 也要拉首帧的额外成本。目标是流量成本与"点开就能播"之间的平衡。

考察视频预加载策略的流量与体验权衡:metadata 只加载元数据省流量,auto 预加载媒体数据提升点播体验但消耗流量;列表页用 metadata、主内容用 auto,可结合视口观察动态提升预加载级别,并用 poster 占位首帧,平衡流量成本与首屏可用性。

<video controls preload="metadata" poster="cover.jpg" src="video.mp4"></video>
#
★★

46. WebGPU 适配器与设备请求在多 GPU 环境下的回退策略与能力检测

WebGPU 的适配器与设备请求在集显/独显多 GPU 环境下如何选择?回退策略与能力检测如何做?

  • requestAdapter 与 powerPreference 的语义
  • 多 GPU 环境下默认适配器的不确定性
  • limits/features 能力检测与设备丢失回退

WebGPU 应用先 navigator.gpu.requestAdapter() 获取适配器,再 requestDevice() 创建设备。多 GPU 环境下,浏览器按策略选择默认适配器:powerPreference 为 high-performance 时倾向独显,low-power 倾向集显与省电,default 由浏览器自行决定;但同一请求在不同设备与浏览器上的结果可能不同,不能假设拿到的是哪块 GPU。工程策略:把适配器选择做成可配置,提供 powerPreference 选项与 UI 设置;用 adapter.limits 与 adapter.features 做能力检测(如 maxBufferSize、纹理格式、扩展支持),按能力分级渲染;requestDevice() 失败或设备丢失(device.lost)时能重建管线并回退到低配模式;不支持的浏览器回退 WebGL2 或 Canvas 2D。关键是"能力探测 + 分级降级 + 可重建",而非假定硬件能力。

考察 WebGPU 多 GPU 环境下的能力管理与回退:powerPreference 影响适配器选择但不保证结果,需按适配器 limits 与 features 分级渲染;requestDevice 失败或设备丢失时要能重建管线并降级到 WebGL,能力探测加可恢复架构是生产级应用的前提,保障渲染链路稳定运行。

const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' });
if (!adapter) fallbackToWebGL();
const device = await adapter.requestDevice();
device.lost.then(() => rebuildPipeline());
#
★★

47. Broadcast Channel 与 SharedWorker 跨标签通信的能力差异

Broadcast Channel 与 SharedWorker 跨标签通信在能力上有什么差异?如何选择?

  • BroadcastChannel 的广播语义与极简 API
  • SharedWorker 的集中式消息路由与状态共享
  • 生命周期与复杂度权衡

BroadcastChannel 提供同源跨标签页的广播通信:new BroadcastChannel(name) 后各上下文可 postMessage 广播,所有同 name 的接收方都能收到,API 极简、无生命周期管理,适合一对多通知(登录状态变化、主题切换、数据更新)。SharedWorker 是集中式共享线程:多个标签页通过 MessagePort 连接同一个 worker,消息由 worker 集中路由,worker 内可持有共享状态(缓存、连接池、计数),适合需要共享计算或状态收敛的场景(共享 WebSocket、全局计数器、去重)。差异:BroadcastChannel 无共享内存与集中逻辑,消息直达、实现简单,但无法维护跨页共享状态;SharedWorker 可共享状态与统一路由,但生命周期由连接维护,通信经 worker 中转有额外延迟,且需处理连接管理。选型:纯通知用 BroadcastChannel,需要共享状态或连接复用用 SharedWorker。

考察跨标签通信机制的差异:BroadcastChannel 广播直达、API 简单且无状态,适合登录态与主题等一对多通知;SharedWorker 集中共享状态并路由消息,适合连接复用与状态收敛;按是否需要共享状态与集中逻辑选择,并注意生命周期管理。

const bc = new BroadcastChannel('auth');
bc.postMessage({ type: 'login', user });
bc.onmessage = ({ data }) => handleAuthChange(data);
#
★★

48. WebRTC 的 insertable streams(Encoded Transform)在端到端加密与特效处理的应用

WebRTC 的 insertable streams(Encoded Transform)如何工作?在端到端加密与特效处理上有什么应用?

  • RTCRtpScriptTransform 插入编解码后的帧流
  • 对编码帧做端到端加密与解密
  • 特效处理、内容审核与密钥分发

Insertable Streams(Encoded Transform)让应用在 WebRTC 媒体管线的编解码器之后、网络传输之前插入自定义处理:通过 RTCRtpScriptTransform 把编码帧(EncodedVideoFrame/EncodedAudioFrame)以流的形式交给 Worker 中的 TransformStream 处理,处理后写回发送/接收管线。应用场景:一是端到端加密,发送端加密编码帧、接收端解密后再交给解码器,实现服务端不可见的媒体加密;二是特效处理,对帧做水印、模糊、滤镜后再发送;三是内容审核与录制,在传输前分析或旁路保存帧。工程要点:密钥需在安全上下文中分发(如通过加密的 DataChannel 交换);Transform 运行在专用 Worker,避免阻塞媒体线程;需处理帧篡改校验与密钥轮换;兼容性上 Chrome 支持较好,其他浏览器需特性检测。

考察 WebRTC 媒体帧流的可编程性:Encoded Transform 在编解码与网络传输之间插入自定义处理,可对编码帧做端到端加密、水印特效与内容审核;变换逻辑运行在专用 Worker,密钥需安全分发并支持轮换,兼容性需特性检测并评估替代方案。

const transform = new RTCRtpScriptTransform(worker, { operation: 'encrypt' });
sender.setEncodedStreams({ send: { transform } });
#
★★

49. SharedWorker 在多标签页共享 WebSocket 连接时的连接复用边界

用 SharedWorker 共享 WebSocket 连接时有哪些边界?消息路由与生命周期如何处理?

  • SharedWorker 持有单一 WebSocket 连接
  • 端口连接管理与消息路由
  • 生命周期、重连与安全边界

用 SharedWorker 共享 WebSocket,可让同源多个标签页复用一条连接:worker 内创建 WebSocket,各标签页通过 MessagePort 连接,worker 把消息路由给对应端口或广播。工程边界:一是生命周期——SharedWorker 由连接的端口计数决定存活,最后一个端口关闭后 worker 终止,WebSocket 随之断开,因此要在 connect 事件中为每个端口绑定消息监听,并跟踪端口关闭,计数归零时主动关闭连接;二是消息路由——需要按连接 ID 或订阅主题分发消息,建立 port 到标识的映射,避免页面间串话;三是重连与状态同步——由 worker 负责断线重连,把最新状态广播给所有页面,页面从 worker 读取而非各自建连;四是安全边界——SharedWorker 只覆盖同源页面,跨源不能共享,共享连接上的请求仍需应用层鉴权。

考察共享连接复用的工程边界:SharedWorker 持有单一 WebSocket 并管理端口生命周期,最后一个端口关闭即销毁;需按端口路由消息、统一处理断线重连与状态广播;同时注意同源限制与应用层鉴权,避免连接复用带来的安全与消息串话问题。

// shared-worker.js
let ws;
self.onconnect = (e) => {
  const port = e.ports[0];
  port.onmessage = ({ data }) => ws?.send(data);
  ws ??= new WebSocket('wss://example.com');
};
#
★★

50. Worker 模块类型(Classic vs Module)

Worker 的经典脚本与模块脚本有什么区别?应如何选择?

  • type: 'module' 支持 import/export 与顶层 await
  • classic 使用 importScripts 同步加载
  • 严格模式、CORS 与构建处理

Worker 创建时可通过 type 指定脚本类型,默认 classic(经典脚本),按传统 script 语义执行,不支持 import/export,只能使用 importScripts() 同步加载外部脚本。type: 'module' 创建模块 Worker:支持 ES module 的 import/export,可静态导入依赖,天然支持命名空间与 tree-shaking,加载语义与主线程

考察 Worker 脚本类型的差异与选型:模块 Worker 支持 import、export 与顶层 await,默认严格模式且跨域加载需 CORS;经典脚本使用 importScripts 同步加载外部脚本;新项目应优先模块类型,并注意构建工具对 worker URL 与 import.meta.url 的处理,避免出现兼容性与打包构建问题。

const worker = new Worker(new URL('./worker.js', import.meta.url), {
  type: 'module',
});
#
★★

51. navigator.hardwareConcurrency 与 crossOriginIsolated 在高吞吐 Worker 池策略的工程价值

navigator.hardwareConcurrency 与 crossOriginIsolated 在 Worker 池设计中有什么价值?

  • hardwareConcurrency 提供逻辑 CPU 核数
  • crossOriginIsolated 决定共享内存可用性
  • 按核数与隔离状态动态调整池与任务队列

navigator.hardwareConcurrency 返回设备的逻辑 CPU 核数,用于估算可并行度;crossOriginIsolated 表示当前页面是否处于跨域隔离状态,决定 SharedArrayBuffer 与 Atomics 是否可用。二者在高吞吐 Worker 池设计中配合使用:一是池大小——通常按硬件并发数创建 Worker(如 min(hardwareConcurrency, 上限)),避免线程过多导致上下文切换开销;二是并发模型——隔离状态下可用 SharedArrayBuffer + Atomics 做共享内存任务队列,Worker 自取任务减少 postMessage 开销,非隔离状态退化为消息传递队列;三是动态调整——根据任务负载与设备核数伸缩池,移动端核数少应降低并行度以省电;四是预留主线程——把计算密集任务卸载到池中,保持 UI 流畅。注意核数可能被浏览器限制(如省电模式),不可过度信任。

考察 Worker 池设计的两个关键输入:hardwareConcurrency 提供逻辑核数用于决定池大小,crossOriginIsolated 决定能否用共享内存队列;隔离状态下可用 Atomics 自取任务降低消息开销,非隔离退化为消息传递,并按设备能力动态伸缩以兼顾性能与功耗。

const poolSize = Math.min(navigator.hardwareConcurrency || 4, 8);
const useSharedMemory = self.crossOriginIsolated === true;
#
★★

52. WebCodecs 在 Worker 中的硬件解码加速与主线程减负

WebCodecs 在 Worker 中如何实现硬件解码加速与主线程减负?有哪些注意点?

  • 在 Worker 中创建 VideoDecoder 解码
  • 帧转移与 OffscreenCanvas 渲染减负主线程
  • 硬件加速能力检测与背压控制

WebCodecs 可在 Worker 中创建 VideoDecoder/VideoEncoder,把编解码从主线程卸载:网络数据在 Worker 中 demux 并喂给 decoder,output 回调得到的 VideoFrame 通过 postMessage 转移(transfer)给主线程绘制,或直接在 Worker 中用 OffscreenCanvas 渲染,主线程只承担 UI。硬件加速通过 hardwareAcceleration: 'prefer-hardware' 启用,支持时用 GPU 解码,显著降低 CPU 占用与功耗,适合高分辨率视频。注意点:一是硬件解码器实例有限,多路并发需排队;二是硬件路径对编码格式与分辨率有兼容限制,需能力检测并用 prefer-software 回退;三是 VideoFrame 转移要用 transfer 列表实现零拷贝,用完在主线程 close();四是控制背压——用 decoder.decodeQueueSize 调节喂帧节奏,避免队列膨胀导致内存峰值。

考察 Worker 中编解码的工程实践:在 Worker 创建解码器并配合帧转移与 OffscreenCanvas 渲染,可大幅减轻主线程负担;硬件加速需能力检测并回退软件路径,同时用解码队列长度控制背压避免内存膨胀,是高清视频处理场景的推荐架构。

// worker 中解码
const decoder = new VideoDecoder({
  output: (frame) => self.postMessage({ frame }, [frame]), // 转移帧
});
await decoder.configure({ codec: 'avc1.42E01E', hardwareAcceleration: 'prefer-hardware' });
#
★★

53. scheduler.postTask 的优先级(user-blocking/user-visible/background)

scheduler.postTask 的优先级如何工作?三个优先级等级分别用于什么场景?

  • postTask 的任务调度与优先级语义
  • user-blocking/user-visible/background 的分级
  • 与 setTimeout、rAF 的对比

scheduler.postTask 是优先级调度 API,通过 postTask(callback, { priority }) 提交任务,浏览器按优先级与调度状态执行。三个等级:user-blocking 面向阻塞用户交互的关键任务(输入响应、首帧渲染),应尽快执行;user-visible 是默认级别,面向用户可见但可容忍轻微延迟的任务(非关键 UI 渲染、普通数据处理);background 面向后台任务(日志、预取、分析),可被推迟且被节流。相比 setTimeout,postTask 由浏览器统一调度,支持优先级、delay 延迟、AbortSignal 取消与优先级动态变更(PriorityChange),且后台行为更可预期;相比 rAF,postTask 不绑定帧循环,适合非渲染任务。工程上按任务对用户体验的影响分级提交,配合信号取消过期任务,避免低优先级任务阻塞关键路径。

考察任务优先级调度的语义:user-blocking 用于关键交互任务尽快执行,user-visible 是默认的可见任务级别,background 面向可推迟的后台任务;postTask 支持优先级、延迟、取消与动态调整,比 setTimeout 更可控,适合按用户体验影响分级调度,提升整体交互响应体验。

scheduler.postTask(() => renderCritical(), { priority: 'user-blocking' });
scheduler.postTask(() => processLogs(), { priority: 'background' });
#
★★

54. PerformanceNavigationTiming 的 activationStart 与 prerender 耗时归因,预渲染对导航时间线的影响

PerformanceNavigationTiming 的 activationStart 是什么?预渲染对导航时间线有什么影响?

  • activationStart 标记预渲染激活时刻
  • 预渲染提前完成加载导致时间线偏移
  • 以 activationStart 为基准归因导航耗时

PerformanceNavigationTiming 描述导航的性能时间线;activationStart 是预渲染相关属性,表示文档从预渲染状态被激活的时刻,非预渲染导航时为 0。当页面被预渲染时,HTML 解析、脚本执行与资源加载都提前到用户真正导航之前完成,导致 navigationStart、domInteractive、loadEventEnd 等时间点远早于用户点击,直接计算会造成"导航耗时"失真甚至为负。正确做法:用 activationStart 作为导航真正开始的基准,计算激活后的各阶段耗时(如从 activationStart 到 first-paint、LCP 的相对时间);也可用它判断页面是否经历了预渲染。工程上把预渲染页面的性能归因单独统计,避免预取流量污染常规导航的性能数据。

考察预渲染对性能时间线的影响:预渲染把解析与加载提前到用户导航之前,导致传统时间点失真;activationStart 标记真实激活时刻,应以其为基准归因各阶段耗时并单独统计预渲染页面,避免预取流量污染导航性能数据与后续优化决策。

const nav = performance.getEntriesByType('navigation')[0];
const realStart = nav.activationStart || nav.startTime;
console.log('激活到 LCP:', lcpTime - realStart);
#
★★

55. navigation.entries() 返回的会话历史条目在跨标签页跳转的工程实践

navigation.entries() 返回什么?在跨标签页跳转场景下有什么工程实践?

  • entries() 返回当前标签页的会话历史条目
  • key/id 标识条目与状态恢复
  • 跨标签页会话历史的独立性

navigation.entries() 返回 NavigationHistoryEntry 数组,代表当前标签页的会话历史:每个条目含 key、id、url、state 与 getState(),key 在同一会话内稳定,id 全局唯一;currentEntryIndex 表示当前位置。工程实践:一是用 key 保存滚动位置与表单状态,路由切换时按 key 恢复;二是用 id 判断条目是否来自同一导航流程,配合 history 属性(auto/push/replace)控制是否新增条目;三是跨标签页跳转时,新标签页拥有独立的会话历史,不会继承源页面的 entries;通过 window.open、location 跳转打开的新页面是全新会话。因此跨标签协作应通过 storage 事件、BroadcastChannel 或显式参数传递状态,而非依赖历史条目共享。

考察会话历史建模与状态恢复:entries 返回当前标签页可遍历的历史条目,key 在会话内稳定可用于滚动与表单状态恢复,id 全局唯一可区分导航流程;跨标签页历史相互独立,状态共享需借助 storage 事件或 BroadcastChannel 等机制实现。

const idx = navigation.currentEntryIndex;
const entry = navigation.entries()[idx];
const saved = entry.getState(); // 恢复该条目的状态
#
★★

56. unload 事件被现代浏览器弃用后如何保证页面离开前的清理逻辑

unload 事件为什么被弃用?页面离开前的清理逻辑应如何实现?

  • unload 不可靠且阻碍 bfcache
  • 用 pagehide/freeze/visibilitychange 替代
  • 保存状态与上报的正确时机

unload 在现代浏览器中不可靠:移动端杀进程、页面切后台、进入 bfcache 等场景都可能不触发;注册 unload 监听器还会让页面无法进入 bfcache,损害前进/后退性能,因此已被逐步弃用。清理逻辑应改用更可靠的生命周期事件:页面离开(卸载或进 bfcache)用 pagehide;被冻结前用 freeze;可见性变化用 visibilitychange。具体实践:保存草稿、关闭 WebSocket、停止动画等清理放在 pagehide 中,并用 event.persisted 区分是否进入 bfcache;上报数据用 navigator.sendBeacon 或 fetch keepalive,保证卸载阶段尽力送达;恢复逻辑放在 pageshow(检查 persisted)中重建连接与状态。不要依赖 unload 做唯一兜底,关键数据应在前台交互时就落盘。

考察 unload 弃用后的生命周期替代方案:unload 在移动端触发不可靠且阻碍 bfcache,应改用 pagehide 处理离开、freeze 处理冻结;保存草稿与上报用 sendBeacon 或 keepalive 保证尽力送达,恢复逻辑放 pageshow 中重建,关键数据应在前台及时落盘,避免用户数据丢失。

window.addEventListener('pagehide', () => {
  saveDraft();
  navigator.sendBeacon('/api/leave');
});
#
★★

57. document.visibilityState 与 document.hidden 在前后台切换的工程协作

document.visibilityState 与 document.hidden 有什么区别?在前后台切换中如何协作?

  • visibilityState 的 visible/hidden/prerender 枚举
  • hidden 是 visibilityState 的布尔简写
  • 前后台切换时的任务暂停与恢复

document.visibilityState 是枚举值 visible、hidden 或 prerender,document.hidden 是它的布尔简写(等价于 visibilityState === 'hidden'),二者来源一致,现代代码推荐直接使用 visibilityState。工程协作:监听 visibilitychange 事件判断前后台切换,在 hidden 时暂停非必要任务(动画、轮询、实时数据订阅、视频渲染),在 visible 时恢复并刷新过期数据;用 visibilityState 识别 prerender 状态以过滤预渲染埋点;后台时 WebSocket 心跳可降频或暂停,回到前台立即补发;播放器在 hidden 时暂停或保留播放状态由产品决定。注意:visibilitychange 不携带 bfcache 信息,判断恢复来源仍需 pageshow.persisted;页面被冻结时还会触发 freeze,应配合 Page Lifecycle 事件统一处理。

考察可见性状态的正确使用:hidden 是 visibilityState 的布尔简写,两者来源一致,推荐直接用 visibilityState 并配合 visibilitychange 做前后台任务的暂停与恢复;它无法识别 bfcache 恢复,需结合 pageshow 的 persisted 属性,并与 freeze 事件配合覆盖完整生命周期,实现完整的生命周期治理。

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') pauseTasks();
  else resumeTasks();
});
#
★★

58. Navigation API 的拦截能力(intercept)如何统一 SPA 与 MPA 路由模型

Navigation API 的 intercept 如何统一 SPA 与 MPA 的路由模型?工程上如何落地?

  • navigate 事件统一覆盖同文档与跨文档导航
  • 同文档导航拦截走前端渲染,跨文档放行
  • 路由匹配、预取与回退到 History API

Navigation API 的 navigate 事件同时覆盖同文档(SPA)与跨文档(MPA)导航,intercept 允许应用在导航发生时统一决策:若目标是当前应用的路由(同源且可拦截),调用 e.intercept() 拦截并走前端渲染,导航不会真正刷新页面;若目标不应由前端接管(外链、下载、整页模式),则不拦截,浏览器按传统 MPA 方式发起跨文档导航。这样路由模型可渐进增强:同一套 navigate 监听既处理 SPA 内部切换,又在需要时放行整页导航。工程落地:用 e.destination.url 与路由表匹配决定是否拦截;拦截 handler 中做数据预取、权限校验与视图过渡(transitionWhile);用 scroll 选项控制滚动恢复;对不支持 Navigation API 的浏览器回退到 history.pushState + popstate 旧模型,实现统一的路由中间件抽象。

考察 Navigation API 统一路由模型的思路:navigate 事件同时覆盖同文档与跨文档导航,intercept 按目标 URL 决定拦截走前端渲染还是放行整页导航;配合路由匹配、数据预取与 transitionWhile,并对旧浏览器回退 History API,可渐进统一 SPA 与 MPA 的路由,降低维护成本。

navigation.addEventListener('navigate', (e) => {
  if (!e.canIntercept || !isAppRoute(e.destination.url)) return;
  e.intercept({ handler: async () => render(e.destination.url) });
});
#
★★

59. requestAnimationFrame 与 requestIdleCallback 在页面生命周期不同阶段的取舍

requestAnimationFrame 与 requestIdleCallback 在页面生命周期的不同阶段应如何取舍?

  • rAF 绑定帧循环,适合渲染与动画
  • rIC 在空闲时段执行低优先级任务
  • 后台与冻结阶段两者的节流行为

requestAnimationFrame 与帧循环绑定,在下一帧绘制前执行回调,适合动画、布局测量与 UI 同步更新,保证视觉流畅;requestIdleCallback 在浏览器空闲时段执行低优先级任务,回调接收 deadline,可通过 timeRemaining() 判断剩余时间并分片执行,适合非关键的数据处理、日志上报与预计算。生命周期阶段取舍:页面可见且需要动画时用 rAF,把重计算拆到 rIC 避免掉帧;页面隐藏后 rAF 停止触发,动画相关逻辑应暂停或改为基于时间戳计算;rIC 在隐藏与冻结阶段也会被节流甚至暂停,不能依赖它保证及时执行,关键任务应降级为定时器或事件驱动;冻结期间两者都不执行。总体原则:渲染与交互用 rAF,空闲填充用 rIC,后台任务不依赖帧回调。

考察两类回调的定位与生命周期行为:rAF 绑定帧循环适合动画与渲染同步,rIC 在空闲时段执行低优先级任务并可分片处理;页面隐藏与冻结时两者都被节流或暂停,关键任务不能依赖它们,应结合事件驱动与定时器做兜底与降级设计。

requestIdleCallback((deadline) => {
  while (deadline.timeRemaining() > 0 && queue.length) {
    process(queue.shift());
  }
});
#
★★

60. pageswap 事件(Chromium 提案)在跨文档导航前预加载资源的应用

pageswap 事件是什么?在跨文档导航前如何用它预加载资源?

  • pageswap 在跨文档导航提交前触发
  • 读取目标 URL 提前预取资源
  • 与 View Transitions 的配合及兼容回退

pageswap 是 Chromium 提出的生命周期事件,在当前文档即将被跨文档导航替换(导航提交前)时触发,开发者可获取 navigationType、to 等导航信息。典型应用:在用户点击外链或表单提交前,用事件回调读取目标 URL,提前向目标页预热资源(如通过 Speculation Rules 或手动 fetch 预热 CDN 缓存、预取关键静态资源),让目标页加载更快;也可在事件中保存当前页面状态或做最后的埋点上报。pageswap 与 View Transitions 配合:跨文档过渡时,旧页快照在 pageswap 后由浏览器接管,可在其中清理监听器、避免内存泄漏。注意:pageswap 只覆盖跨文档导航,同文档 SPA 切换用 Navigation API;事件回调应轻量,不能做耗时同步操作;兼容性需特性检测并回退到 pagehide。

考察跨文档导航前的生命周期钩子:pageswap 在导航提交前触发并可读取目标 URL,适合预取目标页资源、保存状态与配合 View Transitions 清理;它只覆盖跨文档导航,同文档切换用 Navigation API,兼容性有限需特性检测并回退到 pagehide,保证功能可用。

window.addEventListener('pageswap', (e) => {
  if (e.navigationType === 'traverse') return;
  prewarm(e.to.url); // 预热目标页资源
});
#
★★

61. 页面持久化(Page Lifecycle API 的 freeze/resume)对内存与电池的影响

页面的 freeze/resume 对内存与电池有什么影响?工程上如何利用这一机制?

  • frozen 暂停任务降低 CPU 与电池消耗
  • 冻结页占用的内存与恢复开销
  • 冻结前释放资源与 resume 时重建

页面冻结(freeze)是浏览器对长期后台不可见页面采取的资源优化:暂停定时器、requestAnimationFrame、任务队列与网络活动,CPU 占用大幅下降,显著降低移动端电池消耗;同时浏览器可对冻结页做内存压缩或回收,缓解多标签内存压力。代价是恢复(resume)时需重新初始化被暂停的部分,若冻结前未释放资源,恢复会有额外开销与卡顿。工程上:在 freeze 事件中保存关键状态、关闭 WebSocket、提交未完成事务、释放大对象引用,让冻结页占用最小化;在 resume 中重建连接、刷新数据、重启定时器,并避免一次性重放大量任务造成掉帧。也要避免注册 unload 监听器等阻碍冻结的行为;对长时间后台页面,浏览器可能进一步丢弃(discarded)页面,需在 pageshow 中检测 persisted 决定是否整页重载。

考察页面持久化对资源的影响与利用:冻结暂停任务与渲染,显著降低 CPU 与电池消耗,浏览器还可压缩或回收冻结页内存;恢复时需重建被暂停的资源,应在 freeze 前保存状态与释放大对象、resume 时按需重建,并检测 discard 决定是否整页重载。

document.addEventListener('freeze', () => { saveState(); ws?.close(); });
document.addEventListener('resume', () => { restoreState(); connect(); });
#
★★

62. scrollend 事件在惯性滚动结束时的工程应用,如埋点与懒加载触发

scrollend 事件何时触发?在埋点与懒加载触发上有什么工程应用?

  • scrollend 在滚动完全结束后触发一次
  • 与高频 scroll 事件的对比
  • 曝光埋点、懒加载与位置恢复

scrollend 事件在滚动完全结束后触发,包括惯性滚动停止、用户松手、scrollTo 等编程滚动完成;相比 scroll 事件的高频触发,scrollend 只触发一次,且不要求监听 scroll 事件即可感知结束(浏览器在内部滚动结束后派发)。工程应用:一是埋点——滚动结束才上报曝光与浏览深度,避免高频重复上报,配合 IntersectionObserver 统计可见区域;二是懒加载——滚动结束后再触发视口外内容的加载与解码,减少滚动过程中的性能抖动;三是位置恢复与吸附——滚动结束确认最终位置,做分页加载、吸顶吸附或保存滚动位置。注意:scrollend 在部分浏览器(如 Safari)支持较晚,需特性检测并回退到 scroll + 防抖定时器方案;元素级滚动(overflow 容器)同样支持 scrollend。

考察滚动结束事件的工程应用:scrollend 在惯性滚动与编程滚动完全结束后触发一次,适合曝光埋点、懒加载触发与位置恢复;相比高频 scroll 事件开销更低,兼容性不足时用 scroll 加防抖回退,并注意元素级滚动容器同样适用。

container.addEventListener('scrollend', () => {
  reportExposure();
  loadIfNearViewport();
});
#
★★

63. 多 Frame 嵌套(iframe)的页面生命周期事件传播与同源策略边界

iframe 嵌套时页面生命周期事件如何传播?同源策略对生命周期监控有什么边界?

  • 每个 Frame 独立触发生命周期事件
  • 父子 Frame 事件不自动传播
  • 跨源 iframe 的可见性与通信限制

页面由 Frame Tree 组成,每个 Frame(含 iframe)是独立的文档,生命周期事件在各 Frame 内独立触发:父页面隐藏时,其 iframe 也会收到 visibilitychange,但各文档的 visibilityState 相对自身可见性计算;pageshow/pagehide、freeze/resume 在每个文档中独立触发,父子之间不自动传播,父页面无法直接监听子 iframe 内部的事件。同源策略边界:同源 iframe 可通过 contentWindow 访问与监听,跨源 iframe 只能通过 postMessage 通信,无法读取其 DOM 或监听内部事件;跨源 iframe 的可见性可通过 IntersectionObserver 或只读的 visibilityState 间接判断。工程上需在 iframe 内部自行处理生命周期,并通过 postMessage 与父页面同步状态;还要注意 iframe 同样参与 bfcache 与冻结。

考察嵌套文档生命周期与同源边界:每个 Frame 独立触发生命周期事件且不自动传播,父页面无法直接监听子文档内部事件;同源 iframe 可经 contentWindow 访问,跨源只能 postMessage 同步状态,iframe 同样参与 bfcache 与冻结,需在内部自行处理,保证协作逻辑正确。

// iframe 内通知父页面
window.addEventListener('freeze', () => {
  parent.postMessage({ type: 'iframe-freeze' }, origin);
});
#
★★

64. Async Clipboard API(navigator.clipboard.writeText/readText 与图片读写)的权限边界

Async Clipboard API 的读写权限边界是什么?图片读写有哪些限制?

  • writeText/write 需要安全上下文与用户手势
  • readText/read 读取需要 clipboard-read 权限
  • 图片读写经 ClipboardItem 与 Blob 及限制

Async Clipboard API(navigator.clipboard)提供异步读写剪贴板的能力:writeText/write 需要安全上下文与用户手势(通常在一次点击内),否则被拒绝;readText/read 读取内容会触发权限提示,Chrome 需要 clipboard-read 权限,且通常要求页面处于焦点且用户激活。图片读写:write 支持 ClipboardItem 携带 Blob(PNG/JPEG 等),可把图片复制到系统剪贴板;read 返回 ClipboardItem 列表,可读取图片并创建 Blob URL 展示;但浏览器对剪贴板图片格式与来源有限制,部分浏览器只允许读取用户主动复制的内容,跨站点读取可能被阻止。工程注意:必须处理权限被拒与 Promise 失败的回退(document.execCommand 仅作兼容兜底);粘贴图片常用于上传场景,可在 paste 事件中获取 clipboardData.files 作为补充路径。

考察异步剪贴板 API 的权限模型:写入需要安全上下文与用户手势,读取需要 clipboard-read 权限并处于焦点;图片经 ClipboardItem 与 Blob 读写但受来源限制;必须处理权限拒绝,并保留 paste 事件读取 clipboardData 作为补充路径,保证功能可用并提升兼容性。

await navigator.clipboard.write([
  new ClipboardItem({ 'image/png': blob }),
]);
#
★★

65. FileSystemFileHandle 的 move()/remove() 在文件管理类 Web App 的协作

FileSystemFileHandle 的 move() 与 remove() 如何用于文件管理类应用?有哪些协作要点?

  • move() 移动或重命名文件
  • remove() 删除文件或目录
  • 权限、并发冲突与回收站策略

File System Access API 中 FileSystemFileHandle 提供 move() 与 remove():move(targetDir) 把文件移动到目标目录,move(name) 重命名,也可同时指定目录与名称;remove() 删除文件,FileSystemDirectoryHandle 同样支持 remove() 删除目录(可传 recursive 删除子项)。文件管理类应用(网盘、素材库)可据此实现重命名、移动、删除与拖拽整理。协作要点:一是权限——move/remove 需要句柄拥有读写权限,跨目录移动本质是复制后删除,需处理权限丢失与失败回滚;二是并发——多标签页同时操作同一文件时无强制锁,应配合版本号或本地锁避免覆盖冲突,删除前确认;三是撤销——实现回收站而非直接删除,把文件 move 到回收站目录,提供恢复;四是持久化——句柄存入 IndexedDB 以便恢复权限,并在操作后同步 UI 与索引。

考察文件句柄的文件管理能力:move 支持移动与重命名,remove 支持删除文件与目录;工程上需处理跨目录操作的权限与失败回滚、多标签并发冲突、回收站式撤销与句柄持久化,才能构建可靠的文件管理类应用。

const file = await dir.getFileHandle('a.txt');
await file.move(dir2, 'b.txt'); // 移动到 dir2 并重命名
await file.remove(); // 删除
#
★★

66. Font Access API(queryLocalFonts)

Font Access API 的 queryLocalFonts 有什么用途?权限与使用边界如何?

  • queryLocalFonts 枚举设备本地字体
  • 需要用户授权且受安全上下文限制
  • 字体预览、设计工具等应用场景

Font Access API 提供 queryLocalFonts() 让网页枚举设备上的本地字体:返回 FontMetadata 列表(含 family、fullName、postscriptName 等),并可进一步调用 blob() 获取字体文件数据。典型应用:设计工具与编辑器展示字体列表、本地字体预览、字体子集化或与 Canvas 文字渲染结合;相比依赖 Web 字体加载,可即时使用用户本地字体。权限与边界:调用 queryLocalFonts() 需要用户手势并弹出授权对话框,用户可拒绝;字体数据含个人信息(已安装字体可用于指纹识别),浏览器限制在安全上下文使用,授权与页面会话相关;跨 iframe 需考虑权限策略(Permissions-Policy: local-fonts)。注意兼容性有限(Chrome/Edge 支持),需特性检测并降级为内置字体列表或 Web 字体。

考察本地字体访问 API 的用途与边界:queryLocalFonts 枚举设备字体需用户授权,可用于字体预览与设计工具;字体列表含隐私信息有指纹风险,浏览器限制安全上下文且支持有限,需特性检测并降级为内置字体列表或 Web 字体方案。

const fonts = await queryLocalFonts();
const font = fonts.find((f) => f.family === 'Arial');
const blob = await font.blob();
#
★★

67. getDisplayMedia() 屏幕捕获的约束与权限边界,如何同时捕获标签页音频(audio: true)、限制帧率分辨率,以及 region capture 与 restrictOwnAudio 的现代能力?

getDisplayMedia() 如何捕获屏幕?如何同时捕获标签页音频、限制帧率分辨率?region capture 与 restrictOwnAudio 是什么?

  • getDisplayMedia 的 video/audio 约束
  • 帧率、分辨率限制与区域捕获
  • restrictOwnAudio 避免回声

navigator.mediaDevices.getDisplayMedia(constraints) 让用户选择共享屏幕、窗口或标签页,返回 MediaStream;video 约束可限制分辨率与帧率(如 width、height、frameRate: { ideal: 30, max: 30 }),audio: true 可同时捕获标签页音频(仅当用户选择共享标签页时有效)。region capture 允许把捕获的标签页画面裁剪到指定 DOM 区域:通过 CropTarget.fromElement(element) 获取目标,再调用 track.cropTo(target),只采集指定区域,降低带宽与隐私风险;restrictOwnAudio 用于避免捕获自己的音频(如 WebRTC 通话中共享标签页时,防止把远端声音再次采集造成回声),通过 getDisplayMedia({ audio: { restrictOwnAudio: true } }) 或 track.getSettings().restrictOwnAudio 启用。权限边界:每次调用都会弹出系统级选择与授权,无法静默捕获;页面隐藏或用户停止共享时 track 结束。

考察屏幕捕获的约束与边界:video 约束可限制分辨率与帧率,audio 为 true 时捕获标签页音频;cropTo 区域捕获降低带宽与隐私暴露,restrictOwnAudio 防止共享通话时捕获自身音频造成回声;每次捕获均需系统授权,无法静默进行,同时注意保护用户隐私。

const stream = await navigator.mediaDevices.getDisplayMedia({
  video: { frameRate: { max: 30 }, width: { ideal: 1920 } },
  audio: { restrictOwnAudio: true },
});
#
★★

68. AudioWorklet 相较 ScriptProcessorNode 在音频处理线程与低延迟(渲染量子大小)上的优势,以及跨线程传输音频数据的注意事项?

AudioWorklet 相比 ScriptProcessorNode 有什么优势?跨线程传输音频数据有哪些注意事项?

  • AudioWorklet 运行在专用音频渲染线程
  • 128 样本渲染量子带来的低延迟
  • 跨线程传输与回调内开销控制

ScriptProcessorNode 的回调运行在音频线程但缓冲大(4096 样本)、延迟高,且容易因主线程阻塞造成音频卡顿与 glitch;AudioWorklet 运行在专用音频渲染线程(AudioWorkletGlobalScope),回调按渲染量子(128 样本)执行,延迟更低、更稳定,不会阻塞主线程。优势还包括:可编写自定义 DSP 处理、通过 AudioParam 与消息端口(port.postMessage)与主线程通信、按需加载处理器代码。跨线程传输注意事项:音频数据用 ArrayBuffer 转移(transfer)避免拷贝,传输前处理对齐与通道布局;共享状态可用 SharedArrayBuffer 但需处理竞争;不要在音频回调中做耗时操作(分配、I/O、锁),否则造成丢帧;处理器需实现 process() 返回 true 保持活跃,并正确管理输入输出通道数。

考察音频处理架构的演进与工程要点:AudioWorklet 在专用线程按 128 样本量子处理,延迟低且不阻塞主线程;跨线程传输用 ArrayBuffer 转移或共享内存,音频回调内避免耗时操作与锁,process 返回 true 维持活跃,是低延迟音频应用的基础。

// processor.js
class Gain extends AudioWorkletProcessor {
  process(inputs, outputs) {
    const input = inputs[0];
    const output = outputs[0];
    if (input[0]) output[0].set(input[0]);
    return true;
  }
}
registerProcessor('gain', Gain);
#
★★

69. Payment Request API 的完整流程(PaymentRequest 构造与 supportedMethods 支付方法清单、canMakePayment 能力探测、show() 拉起原生支付界面并回传 PaymentResponse)相比跳转收银台与扫码支付在转化率、安全与合规的取舍,以及 Chrome/Edge 与 Safari(Apple Pay)支持差异、Firefox 不支持时的降级方案?

Payment Request API 的完整流程是什么?相比跳转收银台与扫码支付有什么取舍?浏览器支持与降级方案如何?

  • PaymentRequest 构造、canMakePayment 探测与 show()
  • 页面内支付对转化率与安全合规的影响
  • 浏览器支持差异与降级方案

Payment Request API 提供原生支付流程:new PaymentRequest(supportedMethods, details) 声明支付方式(如 basic-card、https://apple.com/apple-pay)与订单明细;canMakePayment() 探测可用性;show() 拉起系统原生支付界面,用户确认后返回 PaymentResponse(含支付凭证与收货信息),再调用 complete() 结束流程。相比跳转收银台与扫码支付:支付不离开页面,流程短、转化率高;凭证在安全环境中传递,降低中间页风险;但接入成本高、浏览器支持不均。兼容现状:Chrome/Edge 支持基于卡的支付并兼容第三方;Safari 主推 Apple Pay;Firefox 长期不支持,需降级——特性检测('PaymentRequest' in window)后回退到服务端收银台跳转或二维码支付,并用 canMakePayment 提前判断能力。

考察原生支付流程与兼容取舍:PaymentRequest 构造、canMakePayment 探测、show 拉起界面、complete 收尾构成完整流程;页面内支付转化率高但支持不均,Firefox 缺失需回退收银台跳转,Safari 侧重 Apple Pay,需按浏览器差异实现降级,同时兼顾体验与支付场景的覆盖广度。

const request = new PaymentRequest(
  [{ supportedMethods: 'basic-card' }],
  { total: { label: '订单', amount: { currency: 'CNY', value: '99.00' } } }
);
const can = await request.canMakePayment();
const response = await request.show();
await response.complete('success');
#

70. Navigation API 的 transitionWhile 在跨文档视图过渡时的视觉衔接

transitionWhile 是什么?在跨文档视图过渡时如何实现视觉衔接?

  • transitionWhile 在 intercept 中驱动 View Transitions
  • 延迟导航提交等待过渡完成
  • 被新导航中断时的清理

transitionWhile 是 Navigation API 与 View Transitions 的桥接方法:在 intercept 的 handler 中调用 e.transitionWhile(promise),浏览器会启动视图过渡,并在过渡期间延迟导航提交或完成,使新旧视图平滑衔接。用法:handler 内部先调 transitionWhile 传入视图更新 Promise,再执行数据加载与渲染;浏览器等待过渡动画结束再完成导航,避免视图突变。与跨文档过渡的协同:页面声明 @view-transition { navigation: auto } 后,导航本身可触发跨文档过渡;transitionWhile 则用于同文档 SPA 切换,把路由更新包装成过渡,二者可组合实现"整站统一过渡"。注意:过渡期间用户可再次导航,新导航会中断旧过渡;应通过过渡的 finished Promise 清理状态,避免在已废弃的过渡上更新 DOM。

考察导航与视图过渡的协同机制:transitionWhile 在 intercept 中启动视图过渡并延迟导航提交,使新旧视图平滑衔接;可与跨文档 view-transition 规则组合实现整站统一过渡;过渡期间新导航会中断旧过渡,需用 finished Promise 清理状态避免残留更新。

navigation.addEventListener('navigate', (e) => {
  e.intercept({
    handler: async () => {
      e.transitionWhile(renderView(e.destination.url));
    },
  });
});
#

71. prerender 与 prefetch 在 CDN 边缘缓存与缓存命中率优化的工程取舍

prerender 与 prefetch 有什么区别?在 CDN 边缘缓存与缓存命中率优化上如何取舍?

  • prefetch 提前下载资源、prerender 完整预渲染页面
  • 对 CDN 缓存预热与命中率的影响
  • 成本控制与按点击概率配置

prefetch 只提前下载资源(页面 HTML 或静态资源)但不执行,浏览器在空闲带宽时获取并缓存;prerender 会完整加载并渲染隐藏页面(执行 HTML、脚本、资源),用户点击时可瞬间呈现。对 CDN 边缘缓存的影响:prefetch/prerender 都会把目标页面与资源"拉"到用户与边缘节点,相当于主动预热 CDN 缓存,提高后续真实请求的命中率、降低回源;对浏览器、CDN、源站多级缓存都能提升命中。取舍:prerender 成本高——完整执行脚本、消耗 CPU 与内存,适合高概率点击的下一页(如搜索结果首项、导航目标);prefetch 成本低,适合低概率但高价值的预取;批量预取过多会挤占带宽与 CDN 配额。工程上用 Speculation Rules 配置 prefetch/prerender 列表,结合 Analytics 预测点击概率,控制预取数量与优先级。

考察预取与预渲染的成本收益取舍:prefetch 只下载资源成本低,prerender 完整渲染成本高但点击即现;两者都能预热浏览器与 CDN 缓存、提升命中率;应根据点击概率与资源价值配置 Speculation Rules,控制预取数量避免挤占带宽与配额。

<script type="speculationrules">
{ "prerender": [{ "source": "list", "urls": ["/next"] }],
  "prefetch": [{ "source": "list", "urls": ["/maybe"] }] }
</script>
#

72. navigation.onnavigate 与 window.onpopstate 在路由拦截能力的对比

navigation.onnavigate 与 window.onpopstate 在路由拦截能力上有什么对比?

  • onnavigate 可拦截并接管导航
  • onpopstate 是历史切换的事后通知
  • 对 SPA 路由可靠性的影响

navigation.onnavigate 是 Navigation API 的导航事件入口,在导航发起时触发,可调用 intercept() 拦截、异步接管、取消或重定向,支持同文档与跨文档导航的统一处理;window.onpopstate 是 History API 在历史条目变化(前进/后退、pushState/replaceState 后)时触发的事件,但只是事后通知:无法拦截、无法取消,也拿不到导航意图(只能读 location 与 state)。对比要点:onnavigate 提供"导航前决策"能力,适合做路由守卫、数据预取与过渡;onpopstate 只能被动响应,且不会在 pushState 调用时触发(需手动派发),SPA 路由需自行维护状态一致性。工程上,新项目优先 Navigation API;旧代码可用 popstate 加手动封装模拟拦截,但可靠性不如原生 intercept。

考察路由事件能力的对比:onnavigate 在导航发起时触发,支持拦截、接管、取消与重定向;popstate 只是历史变化后的事后通知,无法拦截且不覆盖 pushState 调用;新项目应优先 Navigation API,旧代码用 popstate 封装时需补齐事件与状态一致性,保证路由行为一致。

navigation.onnavigate = (e) => {
  if (!canEnter(e.destination.url)) e.preventDefault(); // 取消导航
};
#

73. 页面离开时通过 Beacon API 上报数据相较 navigator.sendBeacon 的兼容性边界

Beacon API 与 navigator.sendBeacon 是什么关系?页面离开时上报有哪些兼容性边界?

  • navigator.sendBeacon 是 Beacon API 的接口
  • 卸载阶段尽力送达与请求体限制
  • 兼容性不足时的回退方案

Beacon API 由 navigator.sendBeacon(url, data) 实现,用于在页面离开(卸载、切后台、进入 bfcache)时向服务器发送少量数据:请求由浏览器接管,不阻塞页面卸载,且不受 unload 阶段 fetch 被取消的影响,能尽力送达。兼容性边界:一是请求体大小受限(通常 64KB 左右),数据过大会失败,需压缩或分片;二是只能发送 POST,content-type 由浏览器决定(文本、表单或 blob);三是部分移动端场景与隐私模式可能不可靠,且页面冻结期间不会发送;四是旧浏览器不支持 sendBeacon 时需回退到同步 XHR(页面即将卸载时同步请求仍可能送达)或图片像素打点。现代补充是 fetch keepalive 与 fetchLater(),支持更大的体量与更灵活的控制。工程上把关键且小体积的离开数据用 sendBeacon 上报,大体积数据提前分片或走 fetchLater。

考察 Beacon 上报的兼容性边界:sendBeacon 由浏览器接管请求、不阻塞卸载且尽力送达,但体量受限且仅支持 POST;移动端与隐私模式可能不可靠,旧浏览器需回退同步 XHR 或像素打点;大数据上报应改用 keepalive 或 fetchLater 并配合服务端幂等。

const ok = navigator.sendBeacon('/api/exit', JSON.stringify(payload));
if (!ok) fallbackSyncXHR(payload); // 失败回退
#

74. 页面恢复时如何识别 bfcache 恢复并选择性地恢复 WebSocket 连接

如何识别页面从 bfcache 恢复?如何选择性地恢复 WebSocket 连接?

  • pageshow 的 persisted 属性识别恢复来源
  • 冻结期间 WebSocket 连接被断开
  • 选择性重连与错失数据补偿

识别 bfcache 恢复的标准方式是监听 pageshow 事件并检查 event.persisted:persisted 为 true 表示页面从 bfcache 恢复,此时 DOM 与 JS 状态保留,但 WebSocket 连接已在冻结期间被浏览器断开,需要重新建立。选择性恢复策略:一是先判断业务是否需要连接——恢复后若仍在前台且功能依赖实时数据则重连;若只是短暂切走,可延迟重连并复用缓存数据,避免连接抖动;二是重连时携带会话标识与服务端校验,防止旧会话失效;三是恢复后同步最新状态——断连期间可能错过消息,重连后应主动拉取增量数据或让服务端补偿推送;四是配合 visibilitychange 与 online 事件判断重连时机,避免网络不可用时反复重试;五是注意恢复时定时器已停止,需重启心跳与轮询。

考察 bfcache 恢复时的连接治理:用 pageshow 的 persisted 识别恢复来源,冻结期间 WebSocket 已被浏览器断开;应按业务是否依赖实时数据选择性重连,重连后主动拉取增量补偿错过的消息,并结合可见性与网络状态决定重连时机,避免无效重试。

window.addEventListener('pageshow', (e) => {
  if (e.persisted && needsRealtime()) {
    reconnectSocket();
    syncMissedData();
  }
});
#

75. navigation.currentEntry 在 SPA 路由状态恢复时的工程应用

navigation.currentEntry 在 SPA 路由状态恢复时有什么工程应用?

  • currentEntry 提供当前条目的 key/id/state
  • 用 key 恢复滚动位置与 UI 状态
  • 前进后退时差异化恢复

navigation.currentEntry 表示当前历史条目,提供 key、id、url 与 getState() 方法;key 在同一会话内稳定,适合作为状态恢复的标识。SPA 路由状态恢复的工程应用:一是滚动位置恢复——路由切换时把 scrollTop 存入 state(或按 key 缓存),返回该条目时用 key 匹配并恢复滚动位置;二是表单与 UI 状态——把草稿、筛选条件、分页写入 state,前进后退时按 key 恢复,避免重新初始化;三是区分导航类型——监听 currententrychange 事件,结合 navigation.entries() 判断是前进、后退还是新导航,分别执行"恢复缓存"与"重新加载"逻辑;四是刷新后恢复——把 key 写入 URL 或 sessionStorage,刷新后用 key 定位历史条目并重建状态。

考察结构化历史条目的恢复应用:currentEntry 提供稳定 key 与 state,可在路由切换时保存滚动位置与表单状态并按 key 恢复;结合 currententrychange 与 entries 判断前进后退方向,配合 URL 或会话存储实现刷新后的状态重建,提升路由体验与数据可靠性。

function saveState() {
  navigation.currentEntry.getState().scroll = window.scrollY;
}
navigation.addEventListener('currententrychange', () => {
  window.scrollTo(0, navigation.currentEntry.getState().scroll || 0);
});
#

76. visibilitychange 事件在前后台切换的埋点暂停与定时器清理策略

前后台切换时如何用 visibilitychange 暂停埋点与清理定时器?有哪些策略?

  • hidden 时暂停埋点采集与上报
  • 清理定时器与后台任务
  • visible 时恢复并补发数据

页面切到后台(visibilityState 变为 hidden)时,浏览器会节流定时器并暂停渲染,若继续采集与上报会造成数据失真与电量浪费。策略:一是在 hidden 时暂停高频埋点采集(如滚动、鼠标轨迹),把待上报数据暂存;二是在 hidden 时清理定时器——clearInterval 轮询、暂停心跳与实时刷新,避免后台空转;三是 visible 时恢复任务并补发——回到前台后重启定时器,把暂存数据合并上报,并刷新过期数据;四是区分业务——播放器、WebSocket 心跳等按需保留最低频率;五是与 Page Lifecycle 配合——页面冻结前(freeze)再做一次最终保存,恢复(resume)时重建;六是上报用 sendBeacon/keepalive 保证离开场景可靠送达。核心是"隐藏即降载、可见即恢复、离开即兜底"。

考察前后台切换的资源治理策略:页面隐藏时应暂停高频埋点与轮询定时器,回到前台恢复并补发暂存数据;播放与心跳按需保留最低频率,冻结前做最终保存,离开场景用 sendBeacon 兜底,形成隐藏降载、可见恢复、离开兜底的闭环。

document.addEventListener('visibilitychange', () => {
  if (document.hidden) { clearInterval(poll); pauseTrack(); }
  else { poll = setInterval(refresh, 5000); flushTrack(); }
});
#

77. Storage Buckets API(navigator.storageBuckets)的多桶隔离策略

Storage Buckets API(navigator.storageBuckets)的多桶隔离策略是什么?有什么工程价值?

  • storageBuckets.open 创建独立存储桶
  • 桶级配额、持久化与耐久性隔离
  • 分级存储与整体清理

Storage Buckets API 允许同一源创建多个相互隔离的存储桶:navigator.storageBuckets.open(name, { persisted, quota, durability }) 创建或打开桶,桶内可访问 IndexedDB、CacheStorage 与 File System Access;各桶有独立的配额、持久化与写入耐久性设置。多桶隔离策略的工程价值:一是分级存储——关键用户数据放入 persisted 桶(用户可手动清除时保留),缓存与日志放入普通桶(可被自动清理);二是配额治理——为不同模块设置不同 quota,避免某个模块占满全部存储;三是清理边界——按桶整体删除过期数据,比逐条清理更高效;四是隐私隔离——第三方内容或子功能使用独立桶,便于按策略重置。注意:桶持久化需用户授权,浏览器仍可能在压力下降级非持久桶;需用 estimate 监控配额。

考察多桶存储隔离的治理价值:桶级配额、持久化与耐久性设置可分级管理关键数据与缓存日志,避免单模块占满存储;按桶整体清理比逐条删除高效,持久化桶需用户授权,配合 estimate 监控配额使用,适合复杂存储架构。

const bucket = await navigator.storageBuckets.open('user-data', {
  persisted: true, quota: 100 * 1024 * 1024, durability: 'strict',
});
const idb = await bucket.indexedDB.open('db');
#

78. navigator.storage.estimate() 在存储配额预警中的应用

navigator.storage.estimate() 返回什么?在存储配额预警中如何应用?

  • estimate 返回 usage 与 quota
  • 计算使用率并分级预警
  • 触发清理与用户提示策略

navigator.storage.estimate() 返回 Promise,包含 usage(当前已用字节)与 quota(可用配额字节),usageDetails 可细分 IndexedDB、CacheStorage 等来源的占用。配额预警应用:一是计算使用率(usage/quota),在超过阈值(如 80%、90%)时预警;二是结合 persist() 判断持久化状态——持久化存储受用户清除影响小,配额策略不同;三是触发清理——按 LRU 清理缓存、删除旧日志与离线数据,优先清理非持久桶;四是用户提示——接近配额时提示用户清理,避免写入失败;五是结合桶级 estimate 定位占用大户。注意 quota 是估计值且可能变化,预警逻辑应容忍波动;写入失败(QuotaExceededError)时也要捕获并做兜底。

考察存储配额监控与预警:estimate 提供 usage 与 quota 及来源明细,按使用率分级预警并触发清理与用户提示;配额是估计值可能变化,写入失败需捕获兜底;结合持久化状态与桶级统计定位占用大户,是存储治理的常规手段。

const { usage, quota } = await navigator.storage.estimate();
const ratio = usage / quota;
if (ratio > 0.9) { promptUserCleanup(); await cleanupCache(); }
#

79. URL.createObjectURL 的内存泄漏治理(revokeObjectURL)与框架层封装策略

URL.createObjectURL 为什么会内存泄漏?如何治理?框架层如何封装?

  • blob URL 持有 Blob 引用占用内存
  • 用后 revokeObjectURL 释放
  • 框架层生命周期封装与自动回收

URL.createObjectURL(blob) 会在内存中登记一个 blob URL,底层持有 Blob 数据引用,若不调用 URL.revokeObjectURL(url) 释放,该内存将一直占用直到页面卸载,长时间预览或上传大量文件会造成内存持续增长,即内存泄漏。治理要点:一是在资源使用完毕后立即 revoke——图片 onload 后、下载完成后、不再显示的预览 URL;二是把 revoke 与资源生命周期绑定,避免遗漏;三是框架层封装——封装 useObjectURL(blob) 之类的 Hook 或指令,在组件卸载、依赖变化时自动 revoke,对批量列表可复用同一 URL 或统一回收;四是避免为同一 Blob 反复创建 URL;五是在 Worker 中创建的 URL 同样需要 revoke。注意:revoke 后 URL 立即失效,正在使用的元素需先完成加载;不能依赖 GC 自动回收。

考察 blob URL 的泄漏治理:createObjectURL 持有 Blob 引用,需在资源使用后调用 revokeObjectURL 释放;框架层可用 Hook 或指令绑定生命周期自动回收,列表场景复用或统一管理 URL,避免为同一 Blob 反复创建,是内存治理的常见实践,避免长期占用浏览器内存。

function useObjectURL(blob) {
  const [url, setUrl] = useState();
  useEffect(() => {
    const u = URL.createObjectURL(blob);
    setUrl(u);
    return () => URL.revokeObjectURL(u); // 卸载时释放
  }, [blob]);
  return url;
}
#

80. File System Access API 与 Web Worker OPFS 在跨标签大文件编辑冲突与锁的工程价值

跨标签页编辑同一文件时如何避免冲突?OPFS 的锁机制与 File System Access API 有什么协作?

  • 文件句柄的并发访问冲突
  • OPFS 同步访问句柄的互斥语义
  • 版本号、锁与冲突合并策略

多标签页同时编辑同一文件时,File System Access API 的文件句柄没有强制互斥:两个标签页可同时读写,后者覆盖前者造成数据丢失;OPFS 的 createSyncAccessHandle 在同一文件上提供互斥语义——同一文件同一时间只能有一个同步访问句柄,第二次获取会抛错,可借此实现编辑锁。工程协作:一是用 OPFS 存储锁文件或编辑状态,标签页编辑前先尝试获取锁,失败则提示"文件正被其他标签页编辑";二是用版本号或内容哈希——每次保存递增版本,保存前校验共享存储中的版本,不一致则提示冲突并合并;三是编辑会话状态用 BroadcastChannel/SharedWorker 广播,实时同步占用情况;四是定期自动保存到 OPFS 草稿,崩溃后从草稿恢复;五是大文件写入用 WritableStream 流式写入,配合锁避免写一半被覆盖。

考察跨标签文件编辑的一致性治理:文件句柄无强制互斥,OPFS 同步访问句柄同一文件只能存在一个,可作编辑锁;配合版本号校验、BroadcastChannel 广播占用状态与自动保存草稿,才能防止并发覆盖并在崩溃后恢复。

let sync;
try {
  sync = await handle.createSyncAccessHandle(); // 抢锁
} catch (e) {
  showLockedTip(); return;
}
// 编辑并保存...
sync.close(); // 释放锁
#

81. showSaveFilePicker 与 WritableStreamDefaultWriter 在浏览器端导出大文件的工程价值

showSaveFilePicker 与 WritableStreamDefaultWriter 如何导出大文件?有什么工程价值?

  • showSaveFilePicker 选择保存位置获取句柄
  • createWritable 与 Writer 流式写入
  • 大文件导出的内存与进度控制

showSaveFilePicker() 让用户选择保存位置,返回 FileSystemFileHandle;调用 createWritable() 获得 FileSystemWritableFileStream,再通过 getWriter() 拿到 WritableStreamDefaultWriter,可把数据分块写入磁盘。大文件导出的工程价值:一是内存可控——数据以流式分块写入(如 fetch 流式读取、生成器逐块产出),无需把整个文件加载到内存,避免导出大文件时 OOM;二是进度与取消——write() 是异步的,可在写入循环中更新进度条,配合 AbortSignal 或 writer.abort() 取消导出;三是原子性——写入完成后调用 close() 落盘,中途失败可删除半成品;四是配合压缩——导出前用 CompressionStream 流式压缩再写入;五是兼容性——不支持 File System Access API 时降级为 加 blob URL(小文件)或分片下载。

考察浏览器端大文件导出的实现:showSaveFilePicker 选择保存位置,createWritable 与 Writer 分块流式写入,内存可控并支持进度与取消,close 完成落盘;不兼容时降级为 blob 下载或分片下载,是数据导出功能的现代实现方式,提升导出功能的可靠性。

const handle = await showSaveFilePicker({ suggestedName: 'data.txt' });
const writable = await handle.createWritable();
const writer = writable.getWriter();
for await (const chunk of sourceStream) {
  await writer.write(chunk); // 分块写入
  updateProgress();
}
await writer.close();
#

82. Multi-Screen Window Placement API(getScreenDetails())在多屏演示、跨屏窗口摆放与 window-management 权限策略下的工程价值与兼容边界?

Multi-Screen Window Placement API(getScreenDetails())在多屏演示、跨屏窗口摆放与 window-management 权限策略下有什么工程价值与兼容边界?

  • getScreenDetails 获取多屏几何信息与插拔事件
  • 跨屏窗口摆放与指定屏幕全屏
  • window-management 权限与浏览器兼容

Multi-Screen Window Placement API 通过 window.getScreenDetails() 获取所有屏幕的详细信息(ScreenDetailed 列表,含位置、尺寸、缩放因子、是否主屏等),并可监听 screenschange 事件感知屏幕插拔;配合 window.moveTo/resizeTo 与 requestFullscreen 指定 screen 参数,可把窗口精确摆放到指定屏幕。工程价值:多屏演示(PPT 在扩展屏全屏、控制面板在主屏)、双屏协作布局、多窗口工作台自动排列。权限策略:API 受 window-management 权限策略控制,调用 getScreenDetails() 会触发授权(可被 Permissions-Policy 与用户设置限制),需处理拒绝;部分能力(如全屏到非主屏)要求用户激活。兼容边界:Chrome/Edge 桌面端支持,Safari/Firefox 与移动端不支持,需特性检测并降级为单屏(window.screen)布局。

考察多屏窗口管理 API 的工程价值与边界:getScreenDetails 提供屏幕几何信息与插拔事件,配合 moveTo 与指定屏幕全屏可实现演示与工作台布局;API 受 window-management 权限控制且仅桌面 Chromium 支持,需特性检测并降级为单屏布局,保证核心功能可用。

if ('getScreenDetails' in window) {
  const details = await window.getScreenDetails();
  const ext = details.screens.find((s) => !s.isPrimary);
  if (ext) window.moveTo(ext.left + 100, ext.top + 100);
}