登录
首页 >  文章 >  前端

原始字符串 length 属性与 Unicode 代理对的冲突,主要体现在字符串长度计算上。在 JavaScript 中,String.prototype.length 返回的是字符串中 16 位码元(code unit)的数量,而不是字符(code point)的数量。一、什么是 Unicode 代理对?Unicode 中的一些字符(如表情符号、某些特殊文字)需要使用 两个 16 位码元 来表示

时间:2026-05-21 18:57:51 493浏览 收藏

JavaScript(及.NET等基于UTF-16的平台)中字符串的`length`属性实际统计的是16位编码单元(code unit)数量,而非人类直观感知的字符(code point)或图形单元(grapheme cluster)个数,这导致包含表情符号、古汉字、奥塞治文等需用代理对(surrogate pair)表示的Unicode字符时,`length`值被错误地翻倍——例如"😊"返回2却仅是一个字符,进而引发截断乱码、正则匹配异常、输入限制误判等真实业务问题;要获得符合认知的字符计数与安全操作,应改用`Array.from()`、`Intl.Segmenter`(支持组合字符与ZWJ序列)或`codePointAt()`等真正按Unicode码点或图形单元处理的现代API。

如何理解原始字符串的 length 属性与 Unicode 代理对(Surrogate Pairs)的冲突

原始字符串的 length 属性并不统计“人眼看到的字符个数”,而是统计 UTF-16 编码单元(code unit)的数量。这个设计在遇到 Unicode 代理对时,就会产生直观上的“冲突”——一个视觉上完整的字符,length 却返回 2。

length 统计的是编码单元,不是字符

JavaScript 和 .NET(如 C# 的 string.Length)都将字符串内部表示为 UTF-16 序列,每个 char 或“代码单元”占 16 位。绝大多数常用字符(如英文字母、常见汉字)只需一个单元,length 值与字符数一致:

  • "a".length → 1
  • "你好".length → 2

但 Unicode 中编号超过 0xFFFF 的字符(如大部分 emoji、古汉字、奥塞治文等),无法用单个 16 位值表示,必须拆成两个连续的单元:一个高位代理(high surrogate,范围 0xD800–0xDBFF),一个低位代理(low surrogate,范围 0xDC00–0xDFFF)。这两个单元合起来才代表一个 Unicode 码点(code point)。

此时 length 会把这两个单元都计入,导致数值翻倍:

  • "?".length → 2(U+1F44D,需代理对)
  • "?".length → 2(U+2070E,增补平面汉字)
  • "?".length → 2(奥塞治字母,同理)

冲突带来的实际问题

这种“统计口径不一致”会在业务逻辑中引发明显异常:

  • 用户输入一个 ?,却被判定为“2 字符”,违反“昵称最多 10 字”限制
  • str.substring(0, 5) 截取前 5 个位置,可能恰好切在代理对中间,返回乱码(如 ""
  • 遍历字符串用 str[i] 取字符时,代理对的高位和低位被当作两个独立“字符”处理
  • charAt()charCodeAt() 同样按 code unit 索引,无法直接获取完整码点

如何绕过这个冲突

要获得符合人类认知的“字符数”,应跳过底层 UTF-16 单元,直接按 Unicode 码点或图形单元(grapheme cluster)计数:

  • 基础准确:用扩展运算符或 Array.from() —— 它们基于字符串的迭代器协议,自动识别代理对
    Array.from("?").length → 1
    [..."?"].length → 1
  • 需要支持组合字符(如 é 写作 e + ◌́)或 ZWJ 连接序列(如家庭 emoji ?‍?‍?‍?):使用 Intl.Segmenter
    new Intl.Segmenter('zh', {granularity: 'grapheme'}).segment("??").length → 1
  • 逐码点操作:用 codePointAt() 替代 charCodeAt(),配合 String.fromCodePoint() 构造字符

C# 中的对应表现

.NET 的 string.Length 行为完全一致:它返回 char 实例数量,而非 Unicode 字符数。例如:

  • "Hello".Length → 5
  • "你好".Length → 2
  • "??".Length → 4(每个奥塞治字母占 2 个 char

若需真实 Unicode 字符数,C# 推荐使用 System.Globalization.StringInfoSystem.Text.Rune 类型(.NET Core 3.0+)来安全遍历码点。

理论要掌握,实操不能落!以上关于《原始字符串 length 属性与 Unicode 代理对的冲突,主要体现在字符串长度计算上。在 JavaScript 中,String.prototype.length 返回的是字符串中 16 位码元(code unit)的数量,而不是字符(code point)的数量。一、什么是 Unicode 代理对?Unicode 中的一些字符(如表情符号、某些特殊文字)需要使用 两个 16 位码元 来表示,这被称为 代理对(Surrogate Pairs)。例如:“😊”(笑脸)在 UTF-16 中由两个 16 位码元组成:0xD83D 和 0xDE0A“👨‍👩‍👧‍👦”(家庭图示)由多个代理对组成二、length 属性的问题当字符串包含这些字符时,length 属性会将每个码元算作一个字符,导致结果不准确。例如:const str = "😊"; console.log(str.length); // 输出 2,但实际是 1 个字符这种差异可能导致以下问题:字符串截断错误(如 str.substring(0, 1) 可能截断到一个无效的码元)正则表达式匹配异常字符计数错误(如密码强度检查、输入限制)》的详细介绍,大家都掌握了吧!如果想要继续提升自己的能力,那么就来关注golang学习网公众号吧!

资料下载
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>