JavaScript文本计数实测:字节、码点与length分别怎么算
一个输入框,为什么需要三个计数
今天制作文本计数小工具时,我把“字数”这个笼统标签拆成了三个明确指标。接口文档常写“最多多少字节”,前端却容易直接用 text.length 校验。对于纯英文,这种写法看起来正常;换成中文或 emoji,就可能出现前端通过、服务端拒绝的情况。
下面用一个无依赖函数和六组实际运行的用例说明边界。这里统计的是字符串本身,不是 HTTP 请求大小,也不是文件在磁盘上的大小。
最小实现
function measureText(text) {
return {
utf8Bytes: new TextEncoder().encode(text).length,
codePoints: Array.from(text).length,
utf16Units: text.length
};
}
三个字段分别回答不同问题:UTF-8 编码后有多少字节、字符串迭代得到多少码点,以及 JavaScript 字符串占多少个 UTF-16 代码单元。不要统一命名为“字符数”,否则读者很难知道限制依据。
六组实测结果
我在本地 Node.js 中用 node:assert/strict 对以下结果逐项断言,六组全部通过。表中转义符表示真实控制字符或组合字符,并非反斜杠文本。
| 输入(JS 字符串形式) | UTF-8 字节 | 码点 | UTF-16 单元 |
|---|---|---|---|
"" |
0 | 0 | 0 |
"abc" |
3 | 3 | 3 |
"中文" |
6 | 2 | 2 |
"😀" |
4 | 1 | 2 |
"e\u0301" |
3 | 2 | 2 |
" A\r\n" |
4 | 4 | 4 |
复制下面的断言即可复现,结果不依赖网络请求:
const assert = require("node:assert/strict");
const cases = [
["", [0, 0, 0]],
["abc", [3, 3, 3]],
["中文", [6, 2, 2]],
["😀", [4, 1, 2]],
["e\u0301", [3, 2, 2]],
[" A\r\n", [4, 4, 4]]
];
for (const [text, expected] of cases) {
const r = measureText(text);
assert.deepEqual([r.utf8Bytes, r.codePoints, r.utf16Units], expected);
}
console.log("6/6 passed");
😀 的结果能直接揭示 length 的边界:一个码点对应两个 UTF-16 单元。组合形式 e\u0301 则提醒我们,即使换成码点数,也不能直接声称统计了用户看到的视觉字符。组合符、连接符和 emoji 序列都需要更谨慎的定义。
接到真实输入框时的两个边界
第一,不要为了“清理输入”先调用 trim()。如果输入前后的空格和换行参与实际业务数据,清理后再计数会低估数据长度。工具应统计当前输入值,清空由用户主动触发。
第二,要区分原始字符串与 textarea 的当前值。浏览器文本输入控件的换行处理可能使原始 CRLF 表现为 LF;因此,粘贴进网页后得到的计数不能拿来证明原文件包含多少字节。上表中的 CRLF 用例直接针对 JS 字符串,网页工具针对当前 textarea 值,两者的输入来源不同。
用于接口限制时,先问清限制对象
如果限制针对 UTF-8 纯文本,可以比较 utf8Bytes。如果服务端限制针对 JSON 请求体,应对序列化后的实际内容计数:字段名、引号、反斜杠转义都会增加字节;纯文本计数无法代替完整请求体计数。其他字符编码、压缩后的传输大小也应另算。
实现小工具时,我只保留三个明确名称的计数、复制统计和清空,不自动做 Unicode 规范化,也不上传或持久化输入。这样的边界比增加更多含义不清的“字数”选项更便于排查接口校验问题。
参考资料
以下文档用于核对 API 定义;代码、测试用例和应用说明为本文整理与实测。
MDN TextEncoder
https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
MDN String: length
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length
- 点赞
- 收藏
- 关注作者
评论(0)