算法与数据结构 / regex · Unicode 字符模型与一个自研引擎 / 三套转义:HTML / CSS / JS 怎么写出一个码位 待审核 3 / 36
源码里写一个码位

三套转义:HTML / CSS / JS 怎么写出一个码位

字符的编码 那页讲清了「码位(code point)」是什么。那么知道了码位——比如 😀 是 U+1F600——怎么在源码里把它写出来?HTML、CSS、JS 各有一套转义语法:😀\1F600\u{1F600}。三套长得像、规则各不同,且各有一个易错点:CSS 的转义会贪心吞掉后面的 hex 字符,HTML 的数字引用不认代理对,JS 的 \uXXXX 工作在码元层、能造出「半个字符」。

1 · 同一个码位的各家写法

三条路各自走完,都该渲染出同一个字符。

图 1-1 · 同一串码位在 HTML / CSS / JS 各处源码里的写法,以及三条路各自渲染的结果。可输入任意字符或点预设。

2 · 易错点一:CSS 转义在哪结束

CSS 的转义是 \ 加 1~6 位 hex,没有花括号包边界——解析器会贪心吃 hex 字符,直到凑满 6 位或撞上非 hex 字符。想在转义后紧跟 A0~9a~f 这类字符,要么加一个终结空格(会被吞掉、不显示),要么把转义补零写满 6 位。

图 2-1 · 同一段 EM DASH 转义的三种写法及其实际解析出的码位。第一种少了终结空格,字母 A 被吃进转义。

注 · 真想显示「转义 + 空格」时,因为终结空格会被吞,要写两个空格,或把空格也转义成 \20。另外 CSS 转义不只用在字符串里——标识符也靠它:id 以数字开头时选择器得写 #\31 st1U+0031),JS 里有现成的 CSS.escape('1st') 可以算。还有一处「直接写码位」的 CSS 语法是 unicode-range: U+4E00-9FFF,见 font-features 的 unicode-range 页

3 · 易错点二:HTML 数字引用只认码位

HTML 的字符引用永远工作在码位层:十进制 😀、十六进制 😀,再大的码位直接写。反过来,把 UTF-16 代理对(字符的编码 里的 D83D + DE00)拆成两个引用拼不出 😀——代理区 U+D800~DFFF 不是合法字符,每个都被替换成 U+FFFD(�)。

图 3-1 · 码位写法与代理对写法交给 innerHTML 解析的结果对照。后者得到两个替换字符。

4 · 易错点三:JS \uXXXX 在码元层

JS 字符串按 UTF-16 码元存,\uXXXX(恰 4 位)写的就是一个码元——所以单写一个高位代理 '\uD83D' 完全合法,得到一个 lone surrogate:长度 1、渲染成 �、传给只收合法 UTF-8 的 API(fetch body、TextEncoder)会被换成 U+FFFD。ES6 的 \u{…} 才工作在码位层。

图 4-1 · 五种写法各自的渲染结果、码元数与 isWellFormed() 读数。可点选写法逐个查看。

注 · 检测与修复:str.isWellFormed() 查字符串里有没有 lone surrogate,str.toWellFormed() 把它们替换成 U+FFFD(ES2024,现代浏览器均已支持,核对于 2026-08)。「为什么 astral 字符要拆成两个码元、怎么算」见 字符的编码

5 · 速查:一个码位的全部写法

表 5-1 · 各处源码里写一个码位的全部写法。经验法则:HTML 用十六进制数字引用(和 U+… 直接对得上)、CSS 记得终结空格、JS 一律用码位层转义;只有 JSON 没得选,astral 必须手拆代理对。
写法 用在哪 位数 astral (>U+FFFF)
😀 / 😀 HTML 文本、属性值 hex / 十进制,任意位 ✓ 直接写码位
— 等命名实体 HTML 文本(仅限有名字的字符) 仅已命名字符
\1F600(+ 终结空格) CSS 字符串、标识符 hex 1~6 位
\u{1F600} JS 字符串 / 模板 / 正则(需 u/v hex 1~6 位
\uD83D\uDE00 JS、JSON(它只有这一种) hex 恰 4 位 = 1 码元 要手拆代理对
\xE9 JS hex 恰 2 位 ✗ 仅 U+0000~00FF
String.fromCodePoint(0x1F600) JS 运行时(读取端是 codePointAt / for...of

建议 · MDN 的 UTF-16 characters, Unicode code points, and grapheme clusters 把本页 JS 半边讲全:String 按码元工作、lone surrogate 的来源(slice 切半也会造出它)、isWellFormed / toWellFormed、按码位遍历。「用户眼里的一个字符」还要再往上一层,见 字素簇

6 · 参考文献

  1. WHATWG. HTML Standard §Named character references. 命名实体那一套的权威清单:逐条列出全部 2200 多个 named character reference 及其码位,并说明哪些可省略结尾分号。html.spec.whatwg.org
  2. Chromium. html_entity_names.csv. 浏览器引擎里这张表的样子:2231 行「实体名 → 码点」,构建期由 make_html_entity_table.py 生成 html_entity_table.h,编译进 Blink 的 entity-matching 状态机。source.chromium.org