跳转至

02 字符串与编码:从「一串 16 位整数」到那个不存在的文件

这一章讲什么

一个日本客户的服务器上——服务器就是一台常年开着、响应请求的程序——程序列出目录里的文件名,然后按名读文件—— readdirSync() 明明刚说这些文件存在,readFileSync() 却抛「文件不存在」1。 这个 bug 跟 JavaScript 语法毫无关系,它的根子在一件事上: 你所以为的「字符串」,在规范里根本不是文字。

这一章沿一条链走到底:规范怎么定义字符串(一串 16 位整数,不保证是文字)→ 文字和数字之间靠什么翻译(字符编码)→ Unicode 为什么有三种编码(UTF-32/16/8, 代理对怎么算)→ 同一张脸为什么有两种拼法(café 事故与规范化)→ Web 标准为什么又分 出三种字符串(ByteString/DOMString/USVString)→ 最后回到那个日本客户:三层各做了什么,缝是怎么接错的,作者是怎么修的。 下一章(03)再往里挖一层:V8 怎么存这些字符串。

顶层全景:一串字节变成「字符串」要经过几道翻译

  磁盘上的原始字节(比如一个文件名)
     │  宿主决定按哪种编码解读  ← 事故高发地
     ▼
  某个编码下的「码点」排法——一列 Unicode 编号
     │  引擎按 UTF-16 规则编码  ← 语言规定的翻译
     ▼
  JavaScript 字符串 = 一串 16 位无符号整数(规范原话)
     │  Web API 视用途再收紧:ByteString / DOMString / USVString
     ▼
  你的代码看到的 'hello'

  图说:每一道箭头都是一次「解读」,解读错了不会报错,只会安静地给你一串错字。

这张图记住一个核心事实:「字符串是什么」这个问题,在每一层有不同的答案。 规范层的答案最宽(任何 16 位整数序列都算),越往上走越窄、越有承诺。 后面每一节就是一层。

核心原理

3.1 规范的定义:字符串是一串 16 位整数,而且不保证是字

翻开 ECMAScript 规范找 String 类型的定义,原话是: String 类型是「零个或多个 16 位无符号整数值('元素')的有序序列」, 最长可以有 2^53-1 个元素2。

第一次读这句话,正常人的反应是:整数?我以为字符串是字符的序列? const str = 'hello',我明明看见的是五个字母。答案:计算机从头到尾只跟数字打交道。 你看见的 hello,机器看见的是五个数字;把数字变成字母的那张对照表, 就叫字符编码(character encoding)——一张「数字 ↔ 符号」的映射表3。

规范定义里还有两个魔鬼细节,都要当场记住:

细节一:每个元素是 16 位,也就是 2 个字节。 经典的 ASCII 编码是单字节编码, 每个符号 1 个字节(8 个二进制位,0–127 共 128 个符号), 比如 97 对应小写字母 a,104 101 108 108 111 拼出 hello4。 元素尺寸对不上(1 字节 vs 2 字节),所以 JavaScript 字符串不可能直接用 ASCII—— 这是 Unicode 登场的直接原因5。

细节二:规范没有要求这些整数对应任何符号。 也就是说, 一段「字符串」可以只是一串映射不到任何文字的数字,书里给这种构造起了个名字: 字节串(byte string)——假装成字符串的原始字节6。 它是合法的 JavaScript 字符串,只是没什么文字意义。 字节串在 JavaScript 里很少见,主要例外是 HTTP 头的名字和值(那要到 fetch 一章才讲)7。

3.2 主线例子:一个字符穿过三层编码,每一步都写出具体数字

现在挑一个具体输入从头走到尾。原书用的主角是符号 ⇇(一对向左的箭头), 它的 Unicode 编号(术语叫码点,codepoint,写作 U+0021C7)。 我们跟着它穿过 Unicode 的三种编码,每步都有具体数字。

第一层:UTF-32,最好懂的编码。 32 的意思是「每个码点固定 4 个字节」。 a 的 UTF-32 编码是 0x00000061——十六进制写法,值正好等于 97,正好是它的码点。 不用任何换算,码点是什么字节就是什么8。代价也直白:编码 hello 要 0x00000068 00000065 0000006c 0000006c 0000006f——ASCII 的 5 个字节膨胀成 20 个, 绝大多数是填零的空位9。日常文本九成是拉丁字母,为每个字母付 4 倍内存, UTF-32 因此被全书判了死刑:「实践中基本不用」10。

第二层:UTF-16,JavaScript 选的编码。 16 的意思是「每个码元(code unit) 16 位」,而一个码点用一个或两个码元表示:编号在 0–65535 之内的码点用一个码元; 超过 65535 的,用一对特殊的码元合起来表示——这对码元叫代理对(surrogate pair)11。

代理对的算法(一套固定的计算步骤),原书给了一组可以直接验证的数。以码点 U+10437(符号 𐐷)为例, 它在 UTF-16 里编码成两个字节序列 0xD801DC37,拆成高代理 0xD801 和低代理 0xDC37, 然后做三步算术:

  高代理的贡献 = (0xD801 − 0xD800) × 0x400 = 0x400   (即 55297 − 55296 = 1,1 × 1024 = 1024)
  低代理的贡献 = 0xDC37 − 0xDC00 = 0x37             (即 56375 − 56320 = 55)
  码点 = 1024 + 55 + 0x10000 = 0x10437              (即 1024 + 55 + 65536 = 66615)

  图说:一对 16 位数字,减两个常数、乘一个常数、相加,还原出一个 21 位的码点。

高代理永远落在 U+D800–U+DBFF,低代理永远落在 U+DC00–U+DFFF, 低代理必须跟在高代理后面12。落单的代理(unpaired surrogate)就是非法序列, 永远还原不出码点,打印出来通常显示成 �13——这个符号后面还会出场。

第三层:UTF-8,网络的通用语。 UTF-8 是变长编码:一个码点占 1 到 4 个字节, 码点编号越小越省14。它的字节排布是一张固定的模板表:

码点范围 字节 1 字节 2 字节 3 字节 4
U+0000–U+007F 0yyyzzzz
U+0080–U+07FF 110xxxyy 10yyzzzz
U+0800–U+FFFF 1110wwww 10xxxxyy 10yyzzzz
U+010000–U+10FFFF 11110uvv 10vvwwww 10xxxxyy 10yyzzzz

表里的 u/v/w/x/y/z 是码点二进制位从高到低的 6 个「4 位小组」。 看出规律了吗:续接字节永远以 10 开头,首字节开头有几个 1 就表示总共有几个字节 (0 开头=1 字节,110=2 字节,1110=3 字节,11110=4 字节)15。 任何不照这个模板的字节序列都是非法 UTF-8——解码器(把字节翻译回字符的程序) 靠这个前缀就能在字节流里随时找到该从哪个字节读起, 这是 UTF-8 适合网络传输的原因16。

现在把主角 ⇇(U+0021C7)塞进这张表走一遍。它的二进制是 0000 0000 0010 0001 1100 0111,按 4 位分组:w=0010、x=0001、y=1100、z=0111, 落在 U+0800–U+FFFF 行,用三字节模板:

  字节 1 = 1110 + w    = 1110 0010 = 0xE2
  字节 2 = 10 + x + y前两位 = 10 0001 11 = 0x87
  字节 3 = 10 + y后两位 + z = 10 00 0111 = 0x87
  结果:0xE2 0x87 0x87

  图说:21 位码点 → 三个字节,首字节标头、后两个字节续接。

这个数字可以用 Node.js 亲手验证:Buffer.from('⇇') 打印出来就是 <Buffer e2 87 87>17。Buffer 是 Node.js 里封装原始字节序列的对象, 能和字符串互转,还能不按文本直接操作字节18——它是本章事故修法的道具。

那到底为什么要有三种编码? 原书给了各自的出生理由:UTF-32 图简单 (定宽、索引快——索引就是按位置直接取用),代价是内存;UTF-16 是折中(常用字符 2 字节,全量靠代理对), 还跟早年 16 位系统兼容;UTF-8 向后兼容 ASCII、对拉丁文本最省、字节序严格, 天生适合网络和跨系统传输19。至于 UTF-16 和 UTF-8 谁更好,至今仍有争论: 中日韩文本在 UTF-16 里更紧凑(常用汉字 2 字节,UTF-8 要 3 字节), 拉丁文本则 UTF-8 完胜20。JavaScript 站 UTF-16:规范规定语言内的文本字符串按 UTF-16 序列构成,这是语言层的要求;而 Web 标准与系统边界(比如 Node.js 的 Buffer 默认行为)按 UTF-8 交换文本21——记住这个分叉,下一节的事故就出在这里。

3.3 同一张脸,两种拼法:规范化与 length 的真相

编码层讲完了,但字符串的坑还没完——Unicode 允许同一个视觉字符用不同的码点序列表示。 原书的例子是 café 里的 é,它有两种合法拼法:

  • 单个码点 U+00E9(一个预制好的「é」);
  • 基本字母 e(U+0065)加一个组合变音符(U+0301)——两个码点叠在一起, 渲染出来一模一样22。

问题来了:JavaScript 比较字符串是逐码元比的,不认「看起来一样」。 所以 'café' === 'café' 是 false,一个 length 是 4、另一个是 523。 .length 数的从来不是「几个字符」,而是几个 UTF-16 码元—— 代理对算两个长度,带组合符的字母每个组成部分都占长度24。 emoji 更夸张:😄 是一个代理对(两个码元),有些 emoji 甚至是多个码点的组合序列25。

解法叫规范化(normalization):Unicode 定义的一个过程, 把等价 的码点序列统一成同一种格式。规范给了四种形式,核心区别两对:

形式 名字 干什么 é 例
NFC 规范组合式 先拆再合,合成预制字符 e+́ → é
NFD 规范分解式 全部拆成基本字符+组合符 é → e+́
NFKC 兼容组合式 像 NFC,连异体也合并 fi(合字) → fi
NFKD 兼容分解式 像 NFD,连异体也拆开 ½ → 1⁄2

JavaScript 每个字符串都带一个 .normalize() 方法,默认输出 NFC, 想换形式就传参数:str.normalize("NFKC")26。

这个「看起来一样」的漏洞是会出安全事故的。原书给的场景:注册系统查重用户名, 不做规范化,用户就能用另一种拼法注册一个肉眼完全相同的用户名—— 论坛、聊天系统里冒充他人27。书里的修复就一行: 比较前先 username.normalize(),存的时候也用规范化后的形式28。 原书还给了四条操作建议:校验之前就规范化(不是之后)、安全攸关的比较用 NFKC (它抓到的等价变体最多)、对关键标识符考虑干脆只允许 ASCII、 在系统边界(API 输入、数据库存储)统一做规范化29。

3.4 正则表达式:不挂 u 旗子就拆散代理对

字符串的这些脾气会传染给正则表达式(一种描述「找什么样的文本」的模式, 写在两条斜杠里,比如 /pattern/)。JavaScript 的正则默认也是按 UTF-16 码元 工作的:不带 u 旗标时,/^./ 匹配 '😀ABC' 的第一个「字符」, 会只咬到 emoji 代理对的前半截,把字符咬残30。

写法 结果
/^./.exec('😀ABC') 只匹配到半个 emoji(损坏)
/^./u.exec('😀ABC') 正确匹配整个 😀
/[😀-😎]/ 直接报错:非法正则
/[😀-😎]/u 正常:按码点算的范围
/\p{Emoji}/u 按 Unicode 属性匹配,必须带 u

原书的结论只有一句:凡是可能碰到 ASCII 以外文本的正则,永远带 u 旗标31。 顺带一个源文件层的提醒:你的 .js 源码文件本身以什么编码保存是要管的—— 多数编辑器默认 UTF-8,但有些默认 UTF-16;源码里写 emoji 之前, 先确认文件编码,否则存进去的字节可能不是你以为的样子32。

3.5 三种 Web 字符串:同一段二进制,三种身份

现在升一层,看 Web 标准(WHATWG 那套浏览器规范)怎么在语言定义之上加约束。 动机很实际:解析 URL、处理 HTTP 头、校验表单输入,对「字符串里的内容合不合法」 的要求各不相同,于是 Web 标准从语言那个宽松的定义里切出三种字符串33:

名字 承诺 用途
ByteString(字节串) 内容是不做文字解读的原始字节;通常是 ASCII 或 Latin1(ASCII 的 256 符号扩展) 受限的二值场景,如 HTTP 头
DOMString 一串 UTF-16 码元,允许含落单的代理 你在 JavaScript 里日常用的就是它
USVString 一串合法 UTF-16 码元;非法序列全部替换成替换符 U+FFFD 解析 URL 等「必须保证能正确解读」的场合

同一个 2 字节序列 0xD800,三种身份给出三种解读34:

  作为 ByteString:就是字节 0xD8 后跟 0x00,不做任何解读
  作为 DOMString:一个非法的、落单的高代理——留着,但宿主没法正确解读
  作为 USVString:落单代理违规 → 替换成 U+FFFD(就是那个 �)

  图说:身份决定解读;USVString 的承诺是"我保证给你能解读的文本"。

把 DOMString 转成 USVString 的最省事办法,是语言自带的 String.prototype.toWellFormed():'\ud800'.toWellFormed() 直接返回 �35。

这个转换在什么地 方真实发生?书里给的实例是 URL 解析。给 URL 解析器喂一个 含落单代理的字符串 new URL('http://example.org/\ud800'),解析算法的内部动作是: 先把非法的 DOMString 转成 USVString(落单代理换成 U+FFFD), 再把结果按 UTF-8 编码、百分号转义,得到合法 URL http://example.org/%EF%BF%BD——%EF%BF%BD 正是 U+FFFD 的 UTF-8 编码的转义写法36。 「把坏输入尽量变成好输出」就是 URL 标准的设计目标。

3.6 回到那个事故:三层各做了什么,作者怎么修的

现在把 3.1 节开头的日本客户案例按三层拆开。事故链是这样的37:

  1. 磁盘与旧系统层:文件名是原始字节序列,操作系统从不解释它。 这批文件来自老系统,文件名按 Shift-JIS(一种 Unicode 之前的日语编码, 至今仍游荡在亚洲的许多老系统里)编码38。
  2. 宿主层(Node.js):当时的 Node.js 文件系统 API 无脑地告诉引擎 「这些字节是 UTF-8」。这一步是宿主的选择,语言对此一无所知—— 规范根本不知道「文件系统」是什么存在39。
  3. 引擎层(V8):按 UTF-8 解码。有些实现的解码器很严格,遇到非法 UTF-8 会拒绝; 而 V8 的解码器相当宽容,会高高兴兴地对非法序列给出一个错误但合法的解读, 除非你显式叫它别这样40。
  4. 结果:readdirSync() 返回的每个名字都对不上磁盘上的真实字节。 拿这个错名字去读文件,操作系统当然说「文件不存在」41。

三层各打五十大板之后,修法也就清楚了:让宿主别再把文件名当文本解读。 作者(当时就在 Node.js 核心团队)做了一个起初颇有争议的改动: 文件系统 API 开始接受 Buffer(原始字节容器),readdirSync() 也可以选 择 用 Buffer 形式返回文件名——加一个 encoding: 'buffer' 选项, Node.js 就彻底停止对文件名做编码猜测,把字节原样交给你42。

这个案例是第 1 章三层框架的第一个完整实战:语言层只看到一串整数, 不知道它是文件名;引擎层的解码策略(宽容 vs 严格)决定了错得多隐蔽; 宿主层的错误假设(文件名必为 UTF-8)才是事故的扳机。 而修复发生在宿主层,因为只有宿主知道「文件名是字节」这个语境43。

3.7 题外但必知:string 还是 String?

书里夹带了一个几乎人人踩过的细节:string 和 String 在 JavaScript 里 是两个不同的数据类型44。小写的是字符串原始值——不可变的基本值, 本章通篇讲的就是它。大写的是装箱字符串(Boxed String)——用 new String("text") 造出来的对象,把一个原始值包进对象里,换来对象式的行为,代价是额外的内存开销45。 书里的建议很直白:永远别用 Boxed String,基本没用,开销也不值46。

作者的判断与证据

有证据的判断:

  • 编码算法的每一步都有可验证的数字:UTF-16 代理对的算式、⇇ 的 0xE2 0x87 0x87、 Buffer.from('⇇') 的实测输出,读者可以亲手复算47。
  • 「V8 的 UTF-8 解码很宽容」是作者以引擎内部实现者身份给出的实现层证词, 与「有的实现会拒绝」形成对照——这是一个关于实现差异的查证型论断48。

作者的主张(经验与建议):

  • 「UTF-32 实践中不该用」「UTF-8 vs UTF-16 看需求」——工程共识,书中没给对比基准, 属于作者的行业判断49。
  • 规范化四条操作建议(校验前规范化、NFKC、考虑纯 ASCII、边界统一做)是原书超 出书内论证的实践清单,属于作者的最佳实践主张,不是 Unicode 规范的要求50。
  • 「永远带 u 旗标」同样是经验法则,反例(纯 ASCII 场景)书中未讨论。

判断(我们的,不是书里的): 本章最有工程价值的一条不是任何编码知识,而是 「比较之前先规范化」——它把一类隐蔽的「看起来相等」bug 变成了流程问题(入口一道工序), 这类问题靠 review 和测试极难穷举。 如果错,会错在: 如果业务本身需要区分 é 的两种码元序列(极少见,如某些排版或 语言学场景),强制 NFC 反而会破坏合法数据——规范化前应确认业务不依赖码元级的区分。

边界与局限

  • 编码数字会过时。 「Unicode 现有约 155,000 个码点」是作者写作时的快照, 每版 Unicode 都在增加(新 emoji、新文字系统)51。
  • 规范引用是 2024 版快照。 ECMAScript 规范持续修订,toWellFormed() 等 相对较新的 API 在旧引擎里不存在——书里没有交代浏览器/Node 的最低支持版本。
  • Latin1、Shift-JIS 等旧编码只给了够用就好的介绍;要真正处理遗留编码, 需要的是 iconv 一类的转换库,书中未指路(因为那一章还没写)。
  • Buffer 只露了一面。它和 TypedArray 的关系、内存池,都要等「二进制数据」 一章——那一章在 MEAP 里尚不存在。

可带走的

  1. 规范定义:JavaScript 字符串 = 最多 2^53-1 个 16 位无符号整数;是文字还是字节, 规范不管。
  2. 编码是「数字 ↔ 符号」的对照表;ASCII 128 个符号 1 字节,装不下全世界,所以有了 Unicode(超过 110 万个码点位,已定义约 15.5 万)。
  3. UTF-32 定宽 4 字节(浪费),UTF-16 每码点 1–2 个码元(超 65535 用代理对), UTF-8 变长 1–4 字节(网络通用);语言内 UTF-16,系统边界 UTF-8。
  4. .length 数的是 UTF-16 码元,不是字符:代理对算 2,组合符各算 1。
  5. 同一个视觉字符可以有多 种码点拼法;比较和存储前先 normalize(), 安全攸关的比较用 NFKC,关键标识符考虑只收 ASCII。
  6. 正则处理非 ASCII 文本必带 u 旗标,否则代理对会被咬成两半。
  7. ByteString(原始字节)/ DOMString(允许坏序列)/ USVString(保证合法, 坏了换 �)——API 文档里见到这三个词,知道它们各自承诺什么。
  8. 文件名是字节不是文本;跨系统传文件名,要么全程定死一种编码,要么干脆传字节。

原文地图

主题 原书章 原文位置
规范的字符串定义 2.1.1 Let’s talk about encodings text/06-ch02-01-2-1-1-let-s-talk-about-encodings.txt:21(搜「set of all ordered sequences」)
字符编码与 ASCII、byte string 同上 text/06-ch02-01-2-1-1-let-s-talk-about-encodings.txt:42(搜「map from a set of numbers」) · :67(搜「byte strings」)
Unicode 目标与码点规模 2.1.2 Unicode, UTF-32, UTF-16, and UTF-8 text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:7(搜「assign a distinct number」) · :13(搜「155,000」)
UTF-32 与 hello 膨胀 同上 text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:37(搜「0x00000061」)
代理对算法与 U+10437 同上 text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:146(搜「0xD801DC37」) · :204(搜「High surrogates」)
UTF-8 模板与 ⇇ 的实测 同上 text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:234(搜「The UTF-8 Encoding」) · :418(搜「0xe28787」)
三种编码的出生理由 同上 text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:491(搜「created to provide」)
语言 UTF-16、边界 UTF-8 同上 text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:526(搜「ECMAScript uses UTF-16」)
café 两种拼法与逐码元比较 同上 text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:561(搜「byte-by-byte」)
四种规范化形式 同上 text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:596(搜「four normalization forms」) · :693(搜「normalize() function」)
用户名查重 bug 与修复 同上 text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:720(搜「duplicate usernames」)
u 旗标与源码编码警告 同上 text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:863(搜「always use the u flag」) · :876(搜「save your JavaScript source」)
三种 Web 字符串 2.1.3 DOMString vs. ByteString vs. USVString text/08-ch02-01-2-1-3-domstring-vs-bytestring-vs-usvstring.txt:25(搜「uninterpreted bytes」) · :92(搜「may include unpaired surrogates」) · :110(搜「valid UTF-16」)
0xD800 的三种解读、toWellFormed 同上 text/08-ch02-01-2-1-3-domstring-vs-bytestring-vs-usvstring.txt:116(搜「three different ways」) · :133(搜「toWellFormed」)
URL 解析的 USVString 转换 同上 text/08-ch02-01-2-1-3-domstring-vs-bytestring-vs-usvstring.txt:188(搜「percent-encoded」)
文件名事故全流程 2.1.4 So, about those “file not found” errors… text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:13(搜「Shift-JIS」) · :46(搜「encoding: 'buffer'」)
V8 解码「相当宽容」 同上 text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:104(搜「quite forgiving」)
string vs String 同上 text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:90(搜「Boxed String」)


  1. 出处:「2.1.4 So, about those “file not found” errors…」第 7 段(text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:7,搜「full circle back」)。事故的完整引子(客户遍历上传目录转格式时撞上「文件不存在」)在章节导言页还有一次更细的讲述;按本书库规矩,导言页属导航内容不作引用,正文以 2.1.4 的复述为准。 ↩

  2. 出处:「2.1.1 Let’s talk about encodings」第 21 段(text/06-ch02-01-2-1-1-let-s-talk-about-encodings.txt:21,搜「set of all ordered sequences」)。2^53-1 约等于 900 万万亿;一个 16 位元素占 2 字节,真顶到上限要 1800 万 TB——所以这条上限只是「理论上的宽」,见 03 章 V8 的实际限制。 ↩

  3. 出处:「2.1.1 Let’s talk about encodings」第 42 段(text/06-ch02-01-2-1-1-let-s-talk-about-encodings.txt:42,搜「map from a set of numbers」)。 ↩

  4. 出处:「2.1.1 Let’s talk about encodings」第 42 段与第 42 段(text/06-ch02-01-2-1-1-let-s-talk-about-encodings.txt:42,搜「The number 97」;:55 搜「104, 101, 108, 108, and 111」)。「单字节编码」的说明在同文件第 61 段(搜「single byte encoding」)。 ↩

  5. 出处:「2.1.1 Let’s talk about encodings」第 79 段(text/06-ch02-01-2-1-1-let-s-talk-about-encodings.txt:79,搜「cannot use the ASCII character encoding」)。另一个现实理由:ASCII 只有 128 个符号且只有拉丁字母,不够国际语言用。 ↩

  6. 出处:「2.1.1 Let’s talk about encodings」第 67 段(text/06-ch02-01-2-1-1-let-s-talk-about-encodings.txt:67,搜「byte strings」)。原文:「a "string" might really be nothing more than a sequence of numbers that do not actually map to symbols at all」。 ↩

  7. 出处:「2.1.1 Let’s talk about encodings」第 73 段(text/06-ch02-01-2-1-1-let-s-talk-about-encodings.txt:73,搜「HTTP header names」)。 ↩

  8. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 37 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:37,搜「0x00000061」)。 ↩

  9. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 43 段与表 2.1(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:43,搜「four times larger」;表在 :49 附近,搜「0x68656c6c6f」)。 ↩

  10. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 491 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:491,搜「rarely used」)。 ↩

  11. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 94 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:94,搜「surrogate pair」)。「一个或两个码元」与「码点范围 0x0000–0xFFFF 用一个码元」都在这一段。 ↩

  12. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 204 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:204,搜「High surrogates are always」)。算式本身在第 146–198 段(搜「0xD801DC37」)。 ↩

  13. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 210 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:210,搜「unpaired surrogate」)。 ↩

  14. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 222 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:222,搜「between 1 and 4 bytes」)。 ↩

  15. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 432–458 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:432,搜「most significant bit」;:458 搜「invalid UTF-8 sequence」)。「几个前导 1 表示几个字节」的读法散见第 444–453 段。 ↩

  16. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 514 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:514,搜「strict byte-ordering sequence」)。 ↩

  17. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 418 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:418,搜「0xe28787」)。原书在 Node.js 里实测:Buffer.from('⇇') 输出 <Buffer e2 87 87>。 ↩

  18. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 471 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:471,搜「encapsulates raw binary data」)。 ↩

  19. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 491–497 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:491,搜「fixed-width」;:494 搜「compromise」;:497 搜「backward-compatible」)。 ↩

  20. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 508 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:508,搜「more compact than UTF-8」)。「汉字 UTF-8 要 3–4 字节」的依据在同段(中日韩文本的比较)。 ↩

  21. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 532 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:532,搜「Language-defined requirement」)。Buffer 默认 UTF-8 的例子在同段代码块(搜「0xc3bf」)。 ↩

  22. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 561 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:561,搜「U+0065 and U+0301」)。 ↩

  23. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 561 段与第 568–586 段的代码块(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:582,搜「false!」;length 的 4 与 5 见同代码块 #D/#E 注释)。 ↩

  24. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 706 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:706,搜「not the actual number of symbols」)。 ↩

  25. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 712 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:712,搜「\ud83d\ude04」)。这是 😄 的代理对字面写法。 ↩

  26. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 596 段与第 596 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:596,搜「normalization forms」;:693 搜「normalize() function」)。四形式的表格在 :602 起(搜「Canonical Composition」)。 ↩

  27. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 720 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:720,搜「duplicate usernames」)。 ↩

  28. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 764 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:764,搜「const normalized」)。 ↩

  29. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 782–794 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:782,搜「Always normalize before」;:791 搜「restricting to ASCII」)。「失败处理会造成真实安全漏洞」在同文件第 776 段(搜「security vulnerabilities」)。 ↩

  30. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 805 段与代码块(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:805,搜「operate on UTF-16 code units」;半 emoji 的结果见 :837 附近代码注释,搜「Matches only half」)。 ↩

  31. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 863 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:863,搜「always use the u flag」)。 ↩

  32. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 876 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:876,搜「save your JavaScript source text files」)。 ↩

  33. 出处:「2.1.3 DOMString vs. ByteString vs. USVString」第 13 段(text/08-ch02-01-2-1-3-domstring-vs-bytestring-vs-usvstring.txt:13,搜「more precision」)。 ↩

  34. 出处:「2.1.3 DOMString vs. ByteString vs. USVString」第 116–128 段(text/08-ch02-01-2-1-3-domstring-vs-bytestring-vs-usvstring.txt:116,搜「three different ways」)。 ↩

  35. 出处:「2.1.3 DOMString vs. ByteString vs. USVString」第 133 段(text/08-ch02-01-2-1-3-domstring-vs-bytestring-vs-usvstring.txt:133,搜「toWellFormed」)。 ↩

  36. 出处:「2.1.3 DOMString vs. ByteString vs. USVString」第 182–188 段(text/08-ch02-01-2-1-3-domstring-vs-bytestring-vs-usvstring.txt:182,搜「new URL」;:188 搜「percent-encoded」)。 ↩

  37. 出处:「2.1.4 So, about those “file not found” errors…」第 13–28 段(text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:13,搜「Shift-JIS」;:19 搜「without interpreting the bytes」;:25 搜「incorrect」)。 ↩

  38. 出处:「2.1.4 So, about those “file not found” errors…」第 13 段(text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:13,搜「predates Unicode」)。 ↩

  39. 出处:「2.1.4 So, about those “file not found” errors…」第 65 段(text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:65,搜「All it sees is the sequence of bytes」)。 ↩

  40. 出处:「2.1.4 So, about those “file not found” errors…」第 104 段(text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:104,搜「quite forgiving」)。 ↩

  41. 出处:「2.1.4 So, about those “file not found” errors…」第 28 段(text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:28,搜「could not be found」)。 ↩

  42. 出处:「2.1.4 So, about those “file not found” errors…」第 33 段与第 33 段(text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:33,搜「controversial change」;:46 搜「encoding: 'buffer'」)。「修复后客户的代码开始正常工作」见第 116 段(搜「began working correctly」)。 ↩

  43. 出处:「2.1.4 So, about those “file not found” errors…」第 110 段(text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:110,搜「known to treat file names」):宿主明知文件名是字节,却天真地让引擎当 UTF-8 解读。 ↩

  44. 出处:「2.1.4 So, about those “file not found” errors…」第 78 段(text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:78,搜「two different data types」)。 ↩

  45. 出处:「2.1.4 So, about those “file not found” errors…」第 90 段(text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:90,搜「Boxed String」)。 ↩

  46. 出处:「2.1.4 So, about those “file not found” errors…」第 96 段(text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:96,搜「avoid using Boxed Strings」)。 ↩

  47. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 146–198 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:146,搜「0xD801DC37」)与第 418 段(搜「simply run it through」)。 ↩

  48. 出处:「2.1.4 So, about those “file not found” errors…」第 104 段(text/09-ch02-01-2-1-4-so-about-those-file-not-found-errors.txt:104,搜「Some implementations of this algorithm are strict」)。 ↩

  49. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 491–520 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:491,搜「rarely used」;:502 搜「fair amount of debate」;:520 搜「depends entirely on your requirements」)。 ↩

  50. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 782–794 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:782,搜「Always normalize before performing validation」)。 ↩

  51. 出处:「2.1.2 Unicode, UTF-32, UTF-16, and UTF-8」第 13 段(text/07-ch02-01-2-1-2-unicode-utf-32-utf-16-and-utf-8.txt:13,搜「155,000 codepoints」)。 ↩