JavaScript 字符串实战:Unicode、搜索、替换与安全输出
本文目录
字符串常见 bug 不只是 API 记错,更是把“索引单位”想当然。JavaScript 的字符串按 UTF-16 码元索引;length 不一定等于 Unicode 码点数,更不一定等于用户看到的字符数。表情、组合音标和旗帜可能由多个码元或码点构成。
搜索、切片和 Unicode
includes 判断是否包含子串,startsWith / endsWith 检查边界,indexOf 返回码元索引,找不到为 -1。slice(start, end) 支持负索引;substring 会把负值按零处理,并在 start 大于 end 时交换参数。需要从字符串末尾按码点读取时可用 at(-1),但它仍不是完整的“用户感知字符”遍历。
用户输入长度限制若按视觉字符计数,应使用 Intl.Segmenter 的 grapheme 分段;格式、搜索或持久化规则也要在前后端使用同一套约定。Unicode 规范化可用 normalize('NFC'),但不要未经业务确认就改变标识符、密码或签名原文。
function countGraphemes(value, locale = 'zh-CN') {
const segmenter = new Intl.Segmenter(locale, { granularity: 'grapheme' });
return Array.from(segmenter.segment(value)).length;
}
const input = 'é 与 👩💻';
const normalized = input.normalize('NFC').trim();
const params = new URLSearchParams({ q: normalized });
const queryString = params.toString();
如需兼容没有 Intl.Segmenter 的运行时,应根据目标环境加入经过测试的 Unicode 分段库或退化策略;不要用 split('') 伪装成正确的字符计数。
替换、正则和格式化
replace(search, replacement) 默认只替换第一个匹配项;全部替换可用 replaceAll,或使用带 g 的正则。把用户提供的文本当作正则表达式,既可能改变匹配语义,也可能造成高耗时回溯;动态正则需转义输入,且限制输入大小。
含 g 或 y 标志的 RegExp 对象会维护 lastIndex;重复调用 test 可能交替得到不同结果。每次测试前重置状态,或者为单次判断使用无状态表达式。复杂文本规则最好用明确的解析器,而不是不断叠加难以审计的正则。
trim 去除首尾空白,不改变原字符串。大小写转换涉及语言规则;面向用户的排序或比较应评估 Intl.Collator,不要假设 toLowerCase 就等同于所有语言环境下的大小写折叠。
需要拆分时用 split(separator, limit);空分隔符按 UTF-16 码元拆分,不适合拆用户感知字符。match、search、matchAll 可配合正则读取匹配结果;matchAll 要求带 g 标志。padStart / padEnd 常用于对齐或固定宽度展示,repeat 会复制字符串指定次数;都要设置合理上限,避免大输入意外分配过多内存。
把任意值转为文本时优先用 String(value),因为 null 和 undefined 没有可调用的实例 toString。charAt / charCodeAt 操作码元;codePointAt 读取当前位置的 Unicode 码点,String.fromCodePoint 与之相对。遍历字符串的 for...of 按码点迭代,仍可能把一个用户感知字符拆成多个码点。旧方法 substr 已不推荐用于新代码,优先用语义更清楚的 slice。
URL 与不可信内容
拼接查询参数时用 URLSearchParams,不要手写百分号编码。编码完整 URL 的组成部分时,区分 encodeURIComponent 与 encodeURI;前者用于单个参数值,后者保留 URL 结构字符。解析现有地址用 URL,并在跳转前校验协议和允许的主机,防止开放重定向。
把文本写入网页时优先用 textContent 或框架的文本插值。innerHTML 会把字符串当 HTML 解析;转义不是 URL 协议校验,也不是完整 HTML sanitizer。用户输入的 Markdown/富文本需要经过适配场景的安全净化,并限制可用标签、属性和链接协议。
交付前自查
- “字符长度”指码元、码点还是 grapheme?前后端规则是否一致?
- 替换是否只改了第一个结果,正则是否有状态或灾难性回溯风险?
- 查询值是否用 URL API 编码,跳转协议/主机是否经过允许列表检查?
- 展示不可信文本是否走纯文本 API,富文本是否经过可信 sanitizer?
- 空字符串、组合字符、emoji、不同语言和超长输入是否覆盖?
参考:MDN String、MDN Intl.Segmenter、Unicode Text Segmentation、WHATWG URL Standard、OWASP XSS Prevention。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。