String.prototype.isWellFormed 是唯一无副作用、零解析开销判断字符串是否含非法代理对的原生方法,返回布尔值,不修改字符串也不抛错,适用于数据持久化前轻量校验。

String.prototype.isWellFormed 是唯一能在运行时**无副作用、零解析开销**判断字符串是否含非法代理对(lone surrogates)的原生方法。它不修改字符串,也不抛错,只返回布尔值——这正是数据持久化前做轻量校验的理想接口。
为什么不能用 JSON.stringify 或正则代替
很多人误以为 JSON.stringify 会暴露非法代理对,其实不会:JSON.stringify 会静默替换非法代理对为 "\uFFFD"(U+FFFD REPLACEMENT CHARACTER),导致脏数据被“修复”后写入数据库,掩盖问题源头。正则如 /[\uD800-\uDFFF]/ 只能匹配单个代理单元,无法区分合法代理对(如 "\uD83D\uDE00" ?)和非法孤立代理(如 "\uD83D"),误报率极高。
-
isWellFormed是 ECMAScript 2024 正式标准,Chrome 120+、Firefox 125+、Safari 17.4+ 支持;Node.js 需 22.0.0+(V8 12.0+) - 它严格遵循 Unicode 标准:仅当字符串中存在未配对的
\uD800–\uDFFF码位时返回false - 对空字符串、纯 ASCII、合法 emoji(含多代理对如 ?
"\uD83E\uDD2C")均返回true
在序列化前插入校验的典型位置
不是所有字符串都需要校验,重点应放在**可能混入外部输入、跨系统传输、或经 DOM API 处理过的字符串**上,比如用户昵称、富文本 HTML 片段、从 Clipboard.readText() 获取的内容。校验应紧邻序列化动作之前,避免中间逻辑污染。
- React 表单提交前:
if (!inputValue.isWellFormed()) { throw new Error("Invalid surrogate pair in input"); } - IndexedDB 写入前:
if (obj.title && !obj.title.isWellFormed()) { console.warn("Dropping malformed title"); delete obj.title; } - fetch 请求体构造时:
const body = JSON.stringify({ content: text.isWellFormed() ? text : null });
常见误用与兼容性兜底方案
直接调用 isWellFormed 在旧环境会报 TypeError: xxx.isWellFormed is not a function。不能靠 try/catch 包裹——那会掩盖真正的类型错误。必须显式检测支持性,并提供低成本 fallback。
- 检测方式必须用
typeof String.prototype.isWellFormed === "function",而非"isWellFormed" in String.prototype(后者在部分 polyfill 下可能为 true 但不可调用) - fallback 不推荐遍历码点(
for (const cp of str) {...}),因for...of本身在含非法代理对时行为未定义;稳妥做法是用Array.from(str).length === str.length——非法代理对会使Array.from拆出更多元素(每个孤立代理算一个),但注意该方法有性能开销,仅用于降级 - Node.js 服务端若需兼容旧版本,建议统一升级到 v22+,避免在服务层引入非标准字符串处理逻辑
真正难的是识别哪些字符串“可能不干净”:DOM 的 textContent 安全,但 innerText 在某些浏览器中可能注入非法代理;WebSocket 收到的文本帧内容必须校验,而 localStorage 读出的字符串如果来源可信可跳过。校验点不在多,而在关键链路的入口处。

















