regex 总览待审核
基础 · 逻辑层与两套存储层

字符的编码:code point、UTF-16、UTF-8

要弄懂「'😀'.length 为什么是 2」「一个汉字占几个字节」这类问题,先得分清两层:code point(逻辑层,一个字符一个号,U+0000 ~ U+10FFFF) 和存储层(字符真正落进内存 / 文件时的字节)。存储层有两套:UTF-16 是 JS 字符串在内存里的样子,每格 16 bit 一个 code unit; UTF-8 是网络 / 磁盘 / 源文件几乎通用的编码,按码点大小用 1~4 个字节变长存。 同一个 code point,两套存储层拆法不同——下面这个拆解器把三层并排摊给你看。

按 code point 拆(for..of / [...str],代理对算一个)

UTF-16 码元(charCodeAt / .length,代理对算两个)

UTF-8 字节(hex + 二进制位,深色是载荷位、灰色是前缀位)

同一串,各存储层各占多少

逐字符速查:想查任意字符 / emoji 的 code point、所属 Unicode block,以及 UTF-8 / UTF-16 / UTF-32 各编码,推荐 symbl.cc —— 完整 Unicode 数据库,一键复制,验证本页拆出来的代理对、码点、字节时拿它对一下很方便。

补充平面 → 代理对的换算

BMP 内的字符一个 UTF-16 码元放得下;补充平面 (astral, > U+FFFF) 放不下,得用两个码元拼, 即代理对 (surrogate pair)。这就是 '😀'.length === 2 的原因。输入里第一个 astral 字符按公式拆成高低位代理:

代理区怎么分工:高位区 U+D800 ~ U+DBFF(1024 个)放 code point 的高 10 bit, 低位区 U+DC00 ~ U+DFFF(1024 个)放低 10 bit。1024 × 1024 ≈ 100 万,正好覆盖 16 个补充平面。 这两段码位专门留给代理、不表示任何字符,所以拼接无歧义。

UTF-8 的前缀位:首字节就写好了「我有几个字节」

UTF-8 把 code point 的二进制位塞进固定模板的 x 位里。首字节前缀的 1 的个数 = 总字节数;续字节恒以 10 开头:

字节数二进制模板载荷位码点范围
10xxxxxxx7 bitU+0000 – 007F(ASCII)
2110xxxxx 10xxxxxx11 bitU+0080 – 07FF
31110xxxx 10xxxxxx 10xxxxxx16 bitU+0800 – FFFF
411110xxx 10xxxxxx 10xxxxxx 10xxxxxx21 bitU+10000 – 10FFFF
自同步 = 抗错。续字节一定是 10xxxxxx,绝不会和任何首字节(0…/110…/1110…/11110…)撞。 所以丢了或损坏一个字节,解码器往后扫到第一个10 开头的字节就能重新对齐——坏处只波及一个字符,用替换符 U+FFFD()顶上,后面照常。 两套存储层对照:😀 在 UTF-16 里是 2 个码元(代理对),在 UTF-8 里是规整的 4 个字节,不需要代理。 代理区 U+D800 – DFFF 只为 UTF-16 而生,在 UTF-8 里非法。两套存储层,同一个 code point——逻辑层永远是那个 U+1F600
ASCII 字节级兼容:U+0000 – 007F 一字节且字节值就等于 ASCII,所以一份纯 ASCII 文本,当 UTF-8 读和当 ASCII 读一模一样——这正是 UTF-8 得以平滑普及的关键原因。

字节序与 BOM:UTF-8 天生不挑字节序

UTF-16 的码元是 16 bit,真正写进文件 / 网络时要拆成 2 个字节——谁在前就是字节序(endianness): big-endian 高位字节在前,little-endian 低位在前。读写两端约定不一致,整篇文本就成乱码。 所以 UTF-16 文本流的开头常放一个 U+FEFF(byte order mark,BOM,可选)声明字节序: 读到 FE FF 就是 BE、FF FE 就是 LE。 而 UTF-8 的码元就是 1 个字节,多字节序列的先后由前缀位模板写死, 没有「高低位谁先」的选择题——避开 UTF-16 这套字节序 + BOM 的麻烦,正是它的设计目标之一。

同一串字符,三种字节流(高位字节 低位字节 BOM)

把 BE 字节流交给解码器,字节序猜对 vs 猜错

为什么偏偏选 U+FEFF 当 BOM?它字节互换后的 U+FFFE 是 Unicode 规定的永久非字符, 正常文本里绝不会出现——所以开头读到 FF FE,只可能是「LE 流的 BOM」,不会误判。 U+FEFF 本职是 zero-width no-break space,只有出现在流开头才当 BOM; 混进文本中间就是一个看不见的空白,典型问题是文件拼接后页面多出不可见的空白行。
UTF-8 的「BOM」:U+FEFF 按 UTF-8 编码是 EF BB BF——它不表示字节序 (UTF-8 没有字节序),只是个「我是 UTF-8」的签名。Unicode 标准既不要求也不推荐; Windows 记事本曾默认写入,常导致 shebang 失效(#! 前多出 3 个字节)、严格的 JSON 解析器报错。

在源码里写一个 code point 的两种写法

JS 字符串字面量里,一个 code point 有两种转义写法,正好对应上面的逻辑层 / 存储层:

写法层级例:汉字「中」/ emoji「😀」
\u{...}逻辑层 code point'\u{4E2D}' / '\u{1F600}'(ES6 起,直接写码点)
\uXXXX存储层 code unit'中' / '😀'(固定 4 位 hex,astral 要手写代理对)
两种写法等价:'\u{1F600}' === '😀',都得到 😀——前者按 code point 写、引擎自动拆成代理对, 后者直接写出上面算过的那对码元。BMP 字符两种写法的 hex 相同('\u{4E2D}' === '中'),只有补充平面字符才看得出差别。 延伸阅读:What every JavaScript developer should know about Unicode 把本页这套字符 / 码点(code point)/ 码元(code unit)/ 代理对的关系从头讲透,还覆盖 \u{...} 转义、String.length 为何按码元算、用 for...of / 展开运算符按 code point 遍历、 以及 normalize() 归一化。 Unicode 之前怎么存中文?本页讲的是 UTF-8 / UTF-16 这套统一方案。在它普及之前,中文靠 GB2312 / GBK / GB18030(大陆,层层超集)和 Big5(台 / 港,繁体)这些国家 / 地区编码把字符变成字节。 中文编码那页把同一个字在五种编码下的字节并排摊开,讲清 GB2312 的区位码、GBK 的「低字节坑」, 以及为什么还要发明 GB18030(用 1/2/4 字节变长,既兼容 GBK 老数据、又无损覆盖全 Unicode)。