Agent Developer Roadmap
Web 基础

Browser / Web APIs:宿主能力、生命周期与安全边界

最后核验:2026-06-30|适用版本:现代常青浏览器、Web Platform Baseline 2024–2026|前置知识:HTML、CSS、JavaScript、TypeScript

浏览器宿主能力与权限边界

ECMAScript 提供语言语义,浏览器提供 Window、Document、网络、存储、Worker、Observer、性能与安全模型。业务代码调用 fetchhistorylocalStorage 时,依赖的是宿主 API,不是 JavaScript 语言本身。

理解宿主边界可以避免三类错误:把 HTTP 404 当 Promise rejection、把 Worker 当可访问 DOM 的后台线程、把 CORS 当服务端鉴权。也能判断一个功能应由 URL、浏览器存储、服务器还是内存状态承担。

Web API 不应为了“原生”而滥用。41 条固定数据不需要 IndexedDB 或 Worker;CSS 容器查询已经解决的布局不需要 ResizeObserver;后端可靠性不能交给页面生命周期事件。先用最小宿主能力,只有数据规模、持久化或性能证据出现后再升级。

Realm、Window 与 Worker 生命周期

Window realm 拥有 document 和页面 UI;Dedicated Worker 有独立全局作用域、事件循环与模块图,没有 DOM;Service Worker 位于网络与页面之间,生命周期独立,适合离线与请求代理,不是常驻后台服务。不同 realm 拥有各自内建对象,跨 realm 的 instanceof 可能不符合直觉,应优先验证结构或使用标准品牌检查。

浏览上下文还包含 origin、active document、history entry 与生命周期状态。iframe 有独立 Window/Document;同源时父子可以访问 DOM,跨源时只能通过受限窗口能力和 postMessage 协作。页面进入 back/forward cache(bfcache)时不会完整销毁,恢复触发 pageshow;初始化和清理不能只假设 load/unload 各一次。

DOM 查询、更新与事件传播

DOM 是宿主对象树,不是 HTML 字符串。查询 API 返回静态或 live collection:querySelectorAll 是静态 NodeList,children/getElementsBy* 常是 live;遍历 live collection 时修改 DOM 会改变集合。创建与更新优先 createElementtextContentreplaceChildren,不可信文本不进入 innerHTML

批量添加节点可使用 DocumentFragment,但现代 append(...nodes)/replaceChildren 已会高效搬移节点;不要为每次渲染先造通用虚拟 DOM。DOM 写入是否触发布局取决于随后是否读取需要最新几何的属性,关键是避免循环中读写交替。

事件按 capture、target、bubble 路径传播。event.target 是原始目标,currentTarget 是当前监听器所在节点;Shadow DOM 可能 retarget,完整路径用 composedPath()stopPropagation 停止后续路径,preventDefault 取消可取消默认动作,两者不是同一件事。

const list = document.querySelector('#results');

list.addEventListener('click', (event) => {
  const button = event.target.closest('button[data-entry-id]');
  if (!button || !list.contains(button)) return;

  const id = button.dataset.entryId;
  if (!id) return;
  console.log('选择条目', id);
});

事件委托减少动态列表 listener,但不是越高越好:在最小稳定容器上注册,验证 closest 结果仍属于容器。addEventListener 选项中 once 自动移除一次监听,passive 承诺不 preventDefault(适合滚动相关事件),signal 可按生命周期批量取消。

const lifecycle = new AbortController();
addEventListener('resize', updateLayout, { signal: lifecycle.signal });
document.addEventListener('visibilitychange', pausePolling, { signal: lifecycle.signal });
// 组件/页面退出:
lifecycle.abort();

自定义事件用 CustomEvent 传递同一页面内松耦合通知,但 detail 仍是共享对象,不是跨线程消息。业务命令更适合直接函数调用;只有确有多观察者和 DOM 边界时使用事件。

Fetch 请求生命周期与 AbortSignal

Fetch Standard 把 Request、Response 与 fetching 过程连接起来,并集成重定向、缓存、CORS、Service Worker 和流。fetch() 在收到 HTTP 404/500 时通常仍 fulfill 为 Response;只有网络层失败、策略阻止或取消等情况才 reject。应用必须检查 response.ok 或状态码。Fetch Standard

这条请求生命周期必须保留错误分类。用户能据此区分“离线”“服务端拒绝”“响应损坏”和“契约变化”;把所有异常显示成“加载失败”会丢掉可操作信息。

AbortController 提供协作取消。把 signal 传给 Fetch 和支持该协议的其他 API;新请求开始、页面离开或组件销毁时调用 abort()。取消不会回滚服务器已经执行的副作用,写请求仍需幂等键或事务语义。

Response body 是一次性可消费流。调用 json()text() 或读取 reader 后,bodyUsed 会变为真;日志和业务都要读取时应谨慎 clone(),因为两个分支的背压与缓存仍有成本。

Request、Headers、凭据与缓存语义

Request 包含 URL、method、headers、body、mode、credentials、cache、redirect 和 signal 等。GET/HEAD 没有请求 body;写操作需设置服务器接受的 Content-Type,并在服务端做 CSRF、认证、授权、幂等和大小限制。浏览器会管理 forbidden headers,应用不能任意伪造 Host/Cookie。

credentials 决定 cookie/TLS client certificate/HTTP auth 凭据是否随请求以及跨源响应的凭据处理。跨源带凭据请求要求服务器返回具体 Access-Control-Allow-OriginAccess-Control-Allow-Credentials,不能用 *。CORS preflight 是浏览器在某些跨源请求前发送 OPTIONS 检查;它保护响应读取边界,不是防止服务器收到请求的权限系统。

HTTP 缓存由 Cache-Control、ETag、Last-Modified、Vary 等响应语义决定,Fetch cache 选项只调整请求如何与缓存交互。no-storereloadno-cache 含义不同:no-cache 允许存储但要求复验,no-store 才要求不存储。业务缓存库不能忽略 HTTP 与 CDN 层,否则会出现多层陈旧和失效不一致。

失败层观察证据是否适合自动重试
DNS/TLS/离线/策略拒绝fetch reject、Network failed幂等读取可带退避;先判断离线/取消
429/503status + Retry-After按服务端节流与幂等规则
401/403status 与认证上下文不盲重试,刷新身份或提示权限
404status 与请求 URL通常是资源/路由问题,不重试
JSON/结构错误body 与验证路径版本/数据故障,不用网络重试掩盖

Streams、背压与增量解析

Response.body 是 ReadableStream。流允许边到达边处理并通过 reader/pipe 链传播背压,适合大文本、媒体和服务端流式响应;小 JSON 直接 response.json() 更短。流被锁定/消费后不能再次读取,tee()/clone 会让慢分支产生缓存压力。

async function readLines(response, onLine, signal) {
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  if (!response.body) throw new Error('响应没有可读流');

  const reader = response.body
    .pipeThrough(new TextDecoderStream())
    .getReader();
  let buffer = '';

  try {
    while (true) {
      signal.throwIfAborted();
      const { value = '', done } = await reader.read();
      buffer += value;
      const lines = buffer.split('\n');
      buffer = lines.pop() ?? '';
      for (const line of lines) onLine(line);
      if (done) break;
    }
    if (buffer) onLine(buffer);
  } finally {
    reader.releaseLock();
  }
}

这不是通用 JSON streaming parser;任意 JSON token 可跨 chunk,需 NDJSON 等明确分隔协议或成熟解析器。服务端若压缩、代理缓冲或 CDN 不 flush,客户端代码也无法保证逐条到达。

URL、History 与可分享状态

URL 是可分享状态边界。用 new URL()URLSearchParams 解析、编码,不手拼查询字符串。history.replaceState 适合频繁同步筛选而不新增历史条目;pushState 适合用户可感知的新导航状态。popstate 时重新从 URL 派生状态,避免 URL 与内存双向漂移。

URL 由 scheme、authority、host、port、path、query、fragment 等组成,解析必须提供可信 base。验证外链时检查解析后的 protocolorigin 和 hostname allowlist,不用 startsWith('https://trusted.example'),后者会接受相似前缀。用户名密码、Unicode/punycode、默认端口和反斜杠都应交给标准解析器。

function readAllowedUrl(input, base = location.href) {
  const url = new URL(input, base);
  if (url.protocol !== 'https:') throw new TypeError('仅允许 HTTPS');
  if (!['docs.example.com', 'api.example.com'].includes(url.hostname)) {
    throw new TypeError('目标域名不在允许列表');
  }
  url.username = '';
  url.password = '';
  return url;
}

History state 会 structured clone,但容量与持久性由浏览器决定,不放大型数据/密钥。pushState 不触发 hashchange,调用本身也不触发 popstate;应用路由必须处理直接导航、刷新、后退前进与服务器 fallback。只更新地址栏不能让服务器理解路由。

页面生命周期、权限与用户激活

页面至少经历 active、hidden、可能 frozen、pagehide/bfcache、discard 等状态。visibilitychange 是暂停轮询/动画的信号,不代表永久离开;pagehideevent.persisted 可提示进入 bfcache;pageshow 恢复时重新确认连接、权限和陈旧数据。避免 unload/beforeunload,只有确有未保存编辑时临时注册 beforeunload。

摄像头、麦克风、地理位置、通知、剪贴板、文件选择、全屏等能力受 secure context、Permissions Policy、用户许可和 transient user activation 共同约束。权限状态可能被用户或系统随时改变;拒绝是正常结果,核心流程必须有替代路径。

能力触发要求数据风险降级
Clipboard write/readHTTPS,读取通常需用户激活/许可读取可能暴露敏感剪贴板可选中文本 + 原生复制
GeolocationHTTPS + 用户许可精确位置与轨迹手工选择地区/地址
getUserMediaHTTPS + 许可 + 设备音视频隐私、track 生命周期文件上传/文字输入
Notifications明确用户意图后请求打扰、跨会话标识页面内状态/邮件设置
File System/Picker用户操作本地文件内容与持久 handle<input type=file>/下载

请求权限不要在页面加载时批量弹窗。先解释用途,在用户执行对应任务时请求,捕获 NotAllowedError/NotFoundError/AbortError 等并停止已获得 MediaStreamTrack。Permissions API 只能查询部分能力,返回 prompt/granted/denied 也不保证下一次调用一定成功。

网络、HTTP、JSON 与契约错误分层

示例区分四类加载错误,使用 AbortSignal,安全写文本,并捕获存储异常。环境为现代浏览器和任意静态服务器。

class HttpError extends Error {
  constructor(status) {
    super(`HTTP ${status}`);
    this.name = 'HttpError';
  }
}

function parseEntries(value) {
  if (!Array.isArray(value)) throw new TypeError('技术目录必须是数组');
  return value.map((entry, index) => {
    if (!entry || typeof entry !== 'object') throw new TypeError(`[${index}] 必须是对象`);
    if (typeof entry.id !== 'string') throw new TypeError(`[${index}].id 必须是字符串`);
    if (typeof entry.name !== 'string') throw new TypeError(`[${index}].name 必须是字符串`);
    return { id: entry.id, name: entry.name };
  });
}

async function loadEntries(url, signal) {
  let response;
  try {
    response = await fetch(url, { signal });
  } catch (error) {
    if (error instanceof DOMException && error.name === 'AbortError') throw error;
    throw new TypeError('网络错误', { cause: error });
  }

  if (!response.ok) throw new HttpError(response.status);

  let data;
  try {
    data = await response.json();
  } catch (error) {
    throw new SyntaxError('JSON 错误', { cause: error });
  }
  return parseEntries(data);
}

const controller = new AbortController();
const status = document.querySelector('[role="status"]');
const list = document.querySelector('ul');

try {
  performance.mark('catalog-load-start');
  const entries = await loadEntries('../../contracts/technologies.json', controller.signal);
  list.replaceChildren(...entries.map(({ name }) => {
    const item = document.createElement('li');
    item.textContent = name;
    return item;
  }));
  status.textContent = `已加载 ${entries.length} 项`;
  performance.mark('catalog-load-end');
  performance.measure('catalog-load', 'catalog-load-start', 'catalog-load-end');
} catch (error) {
  status.textContent = error instanceof Error ? error.message : '未知错误';
}

try {
  document.documentElement.dataset.theme = localStorage.getItem('theme') ?? 'light';
} catch {
  document.documentElement.dataset.theme = 'light';
}

addEventListener('pagehide', () => controller.abort(), { once: true });

把代码接入示例后,从仓库根目录运行:

pnpm --filter @roadmap/web-foundations test
python3 -m http.server 4173

输入:先正常访问示例,再在 DevTools Network 中切换 Offline 后刷新。

预期结果:正常时状态区显示 41 / 41;离线时显示明确网络错误。把 JSON 临时改成非数组会显示数据验证错误;返回 404 会显示 HTTP 状态。无论错误类型如何,页面标题、筛选骨架与无脚本说明仍可阅读。

Storage、IndexedDB 与数据寿命

localStorage 是同步字符串键值存储,可能因隐私模式、配额、策略或无效源抛错;只适合小型偏好,不放令牌、敏感数据或大目录。IndexedDB 是异步、事务式对象数据库,适合较大结构化离线数据;它仍受同源与清理策略约束,也不替代服务器同步。IndexedDB 指南

IndexedDB 数据库由版本、object stores、indexes 和 transactions 组成。schema 只能在 upgradeneeded 的 versionchange transaction 修改;读写必须在事务 active 时排入请求。单个 request success 不代表事务已经提交,最终等待 complete/abort。

function requestToPromise(request) {
  return new Promise((resolve, reject) => {
    request.addEventListener('success', () => resolve(request.result), { once: true });
    request.addEventListener('error', () => reject(request.error), { once: true });
  });
}

function transactionDone(transaction) {
  return new Promise((resolve, reject) => {
    transaction.addEventListener('complete', resolve, { once: true });
    transaction.addEventListener('abort', () => reject(transaction.error), { once: true });
    transaction.addEventListener('error', () => reject(transaction.error), { once: true });
  });
}

async function openCatalog() {
  const request = indexedDB.open('catalog', 1);
  request.addEventListener('upgradeneeded', () => {
    const store = request.result.createObjectStore('entries', { keyPath: 'id' });
    store.createIndex('column', 'column');
  });
  request.addEventListener('blocked', () => {
    console.warn('请关闭仍在使用旧数据库版本的标签页');
  });

  const db = await requestToPromise(request);
  db.addEventListener('versionchange', () => db.close());
  return db;
}

async function saveEntries(db, entries) {
  const transaction = db.transaction('entries', 'readwrite');
  const store = transaction.objectStore('entries');
  for (const entry of entries) store.put(entry);
  await transactionDone(transaction);
}

不要在事务请求之间 await 任意网络/计时 Promise,事务可能自动变为 inactive;先取得外部数据并验证,再开启短事务写入。大迁移采用可恢复版本策略,处理 blocked/versionchange,并在失败后保留旧事实源。

Service Worker、Cache API 与离线更新

Service Worker 运行在独立 worker context,可拦截其 scope 内请求、接收 push/sync 等事件。注册后经历 installing、waiting、activating、activated;新 worker 通常等待旧页面释放控制权。它可能随时终止,事件工作必须用 event.waitUntil() 延长生命周期,不能把内存当持久状态。

Cache API 保存 Request/Response 对,不遵循 HTTP 缓存的自动复验策略,应用自己决定匹配和更新。静态资源可 cache-first,实时 API 常 network-first 或 stale-while-revalidate;带用户身份的响应若错误缓存会跨会话泄露。

// service-worker.js:只缓存明确的同源静态壳
const CACHE = 'roadmap-shell-v1';
const SHELL = ['/', '/styles.css', '/app.js'];

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(SHELL)));
});

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) => Promise.all(
      keys.filter((key) => key !== CACHE).map((key) => caches.delete(key)),
    )),
  );
});

self.addEventListener('fetch', (event) => {
  if (event.request.method !== 'GET' || new URL(event.request.url).origin !== location.origin) return;
  event.respondWith(caches.match(event.request).then((cached) => cached ?? fetch(event.request)));
});

这个最小例子没有离线回退页、更新通知、缓存容量和导航预加载,不能直接当生产策略。生产必须定义版本切换、失败回源、认证响应排除、清理与回滚;skipWaiting/clients.claim 会让新旧代码混用,只有协议兼容时使用。

Worker、Observer 与性能测量的选择

知识浏览器启动时创建一个 AbortController,Fetch 共享 JSON,把 response.json() 的结果保持为 unknown,随后交给 TypeScript 领域验证器。四类错误分别映射为中文状态,避免空列表与加载失败无法区分。

主题是设备偏好,只需一个字符串,使用 localStorage 并以 try/catch 保护;搜索和对比选择需要分享,写进 URL;技术目录是仓库共享事实源,通过 Fetch 读取;当前详情是短暂 UI 状态,只留在内存。不同寿命的数据不塞进同一个 store。

Worker 只在用户点击压力演示后启动。主线程复制 100 份目录,通过 structured clone 发送 { id, entries, query };Worker 复用纯搜索函数并返回结果。启动、脚本加载或执行失败时,终止 Worker 并同步搜索。真实大二进制数据可用 transferable objects 避免复制;普通小对象不需要预先复杂化。

Dedicated Worker 归创建者使用;SharedWorker 可让同源多个上下文经 MessagePort 连接,但生命周期与兼容性更复杂;Service Worker 面向请求代理/后台事件。Web Worker 不访问 DOM,CPU 工作也不会自动变快:启动、模块加载、structured clone 和消息排队都有固定成本。

ArrayBuffer 可作为 transferable 转移所有权,发送后原缓冲区 detached,适合大二进制避免复制。SharedArrayBuffer 允许 agent 共享内存,需要 cross-origin isolation(COOP/COEP)并用 Atomics 建立同步;普通 UI 数据不要承担共享内存竞态。

消息协议要像 API 一样验证类型、版本和 request id:

// 主线程
const worker = new Worker('./search.worker.js', { type: 'module' });
let nextId = 0;

function searchInWorker(entries, query) {
  const id = ++nextId;
  return new Promise((resolve, reject) => {
    const onMessage = (event) => {
      if (!event.data || event.data.id !== id) return;
      worker.removeEventListener('message', onMessage);
      event.data.error ? reject(new Error(event.data.error)) : resolve(event.data.entries);
    };
    worker.addEventListener('message', onMessage);
    worker.postMessage({ type: 'search', id, entries, query });
  });
}

实际实现还要用 AbortSignal 移除 listener/发送 cancel、处理 worker error/messageerror,并在组件销毁终止 worker。只取最新结果时比较 request id,防止慢旧响应覆盖新查询。

跨 Window 通信的 postMessage 必须指定精确 targetOrigin(非敏感公开广播才考虑 *),接收端同时检查 event.originevent.source 和消息 schema。origin 合法不代表用户有业务权限,服务端操作仍重新鉴权。

存储、Worker 与 Observer 选择:

需求首选 API不选它的情况
可分享筛选与选择URL + History敏感值、体积大、仅设备偏好
小型设备偏好localStorage大对象、高频写、跨标签事务
大型离线结构化数据IndexedDB41 条随部署更新的只读 JSON
CPU 密集纯计算Dedicated Worker小列表、需要直接操作 DOM
元素进入视口IntersectionObserverCSS 能完成、需要精确每帧位置
元素尺寸改变ResizeObserver仅视口断点、容器查询已足够
DOM 子树变化MutationObserver自己能在状态更新点直接调用逻辑
自定义性能条目Performance API / Observer无明确指标、只打印耗时日志

Observer 是异步通知,不是业务事件总线。IntersectionObserver 避免持续轮询几何信息,适合懒加载和曝光;ResizeObserver 观察盒尺寸,回调中再次改变被观察尺寸可能形成循环;MutationObserver 观察 DOM 变更,但如果 DOM 完全由当前应用控制,直接在写入点协调通常更简单。Intersection ObserverResize Observer

Network、Performance 与安全检查清单

Network 面板先看请求 URL、method、status、redirect、CORS、缓存和 timing,再看代码。Promise reject 不包含 HTTP 状态是设计行为,不要用网络重试掩盖 401、404 或数据错误。Application 面板检查存储与 Service Worker;Performance 面板用 mark/measure 把业务阶段与主线程任务对齐。

Performance API 的时间戳来自高分辨率单调时钟,performance.mark()measure() 适合业务片段;PerformanceObserver 适合异步消费 navigation、resource、long task 或 Web Vitals 所需条目。采集前定义指标、采样与隐私边界,避免把完整 URL、用户输入或设备指纹上传。

页面隐藏不等于即将永久关闭。visibilitychange 可暂停动画或轮询,pagehide 适合清理并兼容 back/forward cache;不要依赖 unload 可靠保存数据,它可能不执行且影响缓存。重要写入在用户操作时完成,遥测尾包按条件使用 sendBeacon 或 keepalive Fetch。

安全检查清单:

  • 同源策略只隔离不同 origin 的读取,不替代身份认证与授权。
  • CORS 是服务器声明哪些跨源前端可读取响应,不阻止非浏览器客户端发请求。
  • CSP 限制资源与脚本执行来源,不能净化已拼接的不可信 HTML。
  • 权限型 API 在用户动作后请求,拒绝时核心流程有降级。
  • 外部数据保持 unknown,URL 限制允许协议,DOM 使用 textContent
  • localStorage、IndexedDB 与 Cache Storage 不保存长期敏感令牌。
  • Worker 脚本来源、消息结构与 response id 都要验证。
  • 跨窗口消息检查 event.origin 和消息 schema,不使用 * 发送敏感数据。

常见误区:给所有 Fetch 自动重试(会重复非幂等写入);用 mode: 'no-cors' 解决 CORS(得到 opaque response,不能读取内容);把 localStorage 当数据库;每个条目创建一个 Worker;Observer 回调里同步读写大量布局;监听 unload 作为唯一保存机制。

Baseline、特性检测与隐私演进

Web API 由 WHATWG、W3C 与浏览器实现持续演进,应按具体功能和 Baseline 日期记录支持情况。Fetch、URL、History、Web Storage、Dedicated Worker 与主要 Observer 已是现代浏览器支持范围;较新的 Navigation API、Scheduler API、View Transitions 与部分存储能力仍需目标环境和特性检测。

平台方向是统一取消、流与异步迭代:更多 API 接受 AbortSignal,Fetch body 使用 Streams,Observer 批量异步交付。应用层应复用这些协议,而不是为每个模块设计不兼容的取消 token 和事件包装。

隐私保护会继续影响存储分区、第三方 cookie、精确时间和权限。任何依赖跨站身份或永久客户端存储的设计,都必须把浏览器清理、用户拒绝和分区视为正常状态,而非异常边角。

采用新 API 的最低路径:if ('Feature' in globalThis) 或方法级检测,保留可用基础版本,记录手动浏览器验证范围。不要根据 User-Agent 列表维护分支,也不要为一个装饰性增强下载大型 polyfill。

持久化事务、并发与恢复语义

选择存储不能只看容量。localStorage/sessionStorage 是同步字符串表,没有事务隔离;IndexedDB 事务把一组 object store 操作绑定为原子提交,但事务会在事件循环控制权和请求活动状态变化时自动结束;Cache API 保存 Request/Response 对,适合离线资源,不提供业务记录查询;服务器持久化才负责跨设备、权限、备份和一致性。

能力sessionStoragelocalStorageIndexedDBCache API服务端
生命周期当前标签页会话同源、可被用户清理同源、可被清理同源、可被清理由产品策略决定
同步模型同步同步异步请求 + 事务Promise网络协议
结构与查询字符串键值字符串键值object store、index、cursorRequest → Response数据库/服务定义
并发协调标签页隔离跨页通知但非事务锁事务与版本升级阻塞最后写入取决于应用锁、事务或版本控制
失败恢复重建短暂状态解析失败时回退默认处理 abort、quota、blocked、versionchange缓存未命中后回源重试、幂等、备份与审计
安全边界脚本可读脚本可读脚本可读脚本/Service Worker 可读认证、授权与加密策略

IndexedDB 版本升级是最容易被忽略的严重故障:其他标签页保持旧连接时,新连接会收到 blocked;旧连接应监听 versionchange 并关闭,否则发布后的用户可能永久卡在升级界面。写事务的 complete 才表示提交成功,单个 request 的 success 不等于整个事务已落盘。发生 QuotaExceededError、用户清理或隐私分区变化时,应能从服务端或随应用发布的数据重建,而不是把浏览器存储当唯一事实源。IndexedDB 3.0

跨标签并发若只需通知可用 BroadcastChannel;需要互斥可评估 Web Locks,但锁不保证进程崩溃后的业务一致性。支付、库存、权限和不可丢失编辑仍必须把最终事务放在服务端,并使用版本号、幂等键或冲突合并协议。

状态寿命与平台 API 决策表

状态或工作内存URLlocalStorageIndexedDBWorker
当前对话框首选不必要不必要不必要不适用
可分享查询可作运行副本首选会产生双真值过重不适用
主题偏好降级不适合首选过重不适用
大型离线目录缓存当前视图不适合同步且容量有限首选可辅助计算
4,100 条纯搜索保存结果保存查询不适合可选数据源经测量后可选

平台 API 与框架不是同层替代品。框架最终仍调用 Fetch、History、Storage 和 DOM;先理解宿主契约,才能判断框架封装是在减少重复还是隐藏关键错误和生命周期。

请求、存储与 Worker 验收练习

  • 分别模拟网络失败、HTTP 500、无效 JSON 和字段错误,核对四种状态。
  • 快速发起两次搜索,取消第一次并确认取消不显示为网络故障。
  • 用 URL 恢复查询、专栏和四项选择,再用后退/前进验证状态同步。
  • 禁用站点存储,确认主题回退内存且目录仍可用。
  • 比较 41 条与 4,100 条搜索的主线程、Worker 耗时和输入响应。
  • 为同一需求分别尝试容器查询与 ResizeObserver,只保留更小方案。
  • 用 Performance mark/measure 记录加载和渲染,不上传敏感参数。
  • 审查页面 CSP、CORS、外链和跨窗口消息边界。

验收标准:能从请求、策略、解析、验证到渲染逐层定位失败;能按状态寿命选择存储;能解释 Worker/Observer 的成本与降级,而不是把所有浏览器 API 都当优化开关。

Web 平台规范与测量资料

在 GitHub 上编辑此页

本页目录