Web 平台 API / HTML 表单:从控件到提交与校验 / maxlength 数的是 UTF-16 code unit,不是字符 待审核 16 / 24
maxlength · code unit

maxlength 数的是 UTF-16 code unit,不是字符

maxlength="6" 读起来像「最多 6 个字」,但规范说的是「值的长度不超过 6」,而这个长度指的是 UTF-16 code unit 数——也就是 JS 里的 str.length。对 ASCII 与多数中文字符,一个字符正好是一个 code unit,差别看不出来;一旦遇到 emoji、罕用汉字、组合字符,「字符数」和「code unit 数」就会分道扬镳。

1 · 对着一个真实的 maxlength 输入框打字

下面是一个 <input maxlength="8">。试着粘贴或用按钮塞入 emoji——可以发现远不到「8 个字」就被拦住,因为浏览器拦的是第 8 个 code unit

图 1-1 · maxlength 为 8 的输入框。粘贴或插入 emoji 时远不到八个字就被拦住,因为拦的是第八个 code unit。

2 · 逐字素拆开:每个「字」占几个 code unit

把任意一段文本逐 字素 (grapheme) 拆开,标出每个字素由哪些 code point 组成、合计几个 UTF-16 code unit。边框更醒目的格子表示这个「字」不止 1 个 code unit——它在 maxlength 眼里被算作多个:

图 2-1 · 把文本逐字素拆开,标出每个字素的 code point 组成与合计的 UTF-16 code unit 数,醒目边框表示该字不止一个 code unit。

为什么是 UTF-16? JavaScript 字符串、DOM 的文本内容在内存里都以 UTF-16 表示。基本多文种平面 (BMP) 内的字符占 1 个 code unit;之外的字符(emoji、𝕏、罕用字…)用一对 surrogate 表示,即 2 个 code unit。str.length 数的是 code unit,所以 '😀'.length === 2maxlength 直接沿用了这套长度。Unicode 字符模型的完整脉络见 Regex × Unicode 系列。

3 · 几个连带的陷阱

情形 行为
脚本赋值 el.value = '...' maxlength 不拦。它只约束用户输入与粘贴,程序赋值可超长——提交前需自行校验。
<textarea> 里的换行 maxlength 量的是 API value,其中换行已归一为 LF(1 个 code unit,实测 maxlength=5 接受 a\nb\nc);而提交用的 value 把换行写成 CRLF。所以计数比提交报文,两行文本的提交串会比 maxlength 多出换行数个字节。
type="number" 上的 maxlength 无效。数字类控件不认 maxlength,要限位数得用 min / max 或改 type="text" inputmode="numeric"(见 inputmode 页)。
想按「肉眼字数」限制 HTML 没有现成属性。需 JS 监听 input,用 Intl.Segmenter 数字素后截断——但要小心别把 surrogate pair 拦腰截断成乱码。