ES 中的 Unicode
ES 采用的字符集
ES 采用 Unicode 字符集。
ES 使用的 Unicode 编码方案
- 1995 年 5 月,JavaScript 设计完成。当时只有 UCS-2 一种编码方案可用。
- 1996 年 7 月,UTF-16 编码方案发布。
- 1996 年 10 月,第一个 JavaScript 解释器问世。由于语言设计已经完成,解释器依从设计来实现,所以同样采用了 UCS-2 编码方案。
- 1996 年 11 月,Netscape 向 ECMA 提交语言标准。同样的,仍旧采用 UCS-2 编码方案。
这样来讲,ES 使用的是 UCS-2 编码方案。
但是,UTF-16 编码方案发布时,明确宣布是 UCS-2 编码方案的超集(即基本多语言平面 Code Point 沿用 UCS-2 编码方案的 2 字节编码,辅助平面 Code Point 使用 4 字节编码)。UCS-2 被整合进 UTF-16 后,UTF-16 成为事实标准。
综上,ES 使用的是 UTF-16 编码方案。
从 ES 3 开始,标准文档中就已经开始涉及 UTF-16 相关的描述。
ES 中字符处理的局限
ES 使用的是 UTF-16 编码方案,按理来说,是可以处理 4 字节编码的。但是由于上一节提到的历史原因,ES 2015 之前的标准中所定义的字符处理函数只能处理 2 字节编码,4 字节编码会被当成两个 2 字节编码来处理。
以辅助平面字符 𠀾 为例:
-
Unicode 码点为
U+2003E
-
UTF-16 编码为
0xD840DC3E
在处理这个字符时会遭遇诸多问题:
const c = '𠀾'
c.length // 2 错误:字符长度应该为 1
c.charAt(0) // � 错误:第一个字符应该为 𠀾
c.charCodeAt(0) // 55360(0xD840) 错误:第一个字符对应的编码应该为 0xD840DC3E
类似的问题存在于 ES2015 之前的所有字符处理函数中:
-
String.prototype.replace()
-
String.prototype.substring()
-
String.prototype.slice()
- …
要解决字符处理的这些问题,只能依照 UTF-16 编码方案自行处理。
ES 2015 增强对 UTF-16 的支持
正确识别 4 字节编码
迭代器支持:
for (const s of string) {
}
为了保持兼容性,length 属性仍然保持原来的行为。为了得到字符串的正确长度,可以使用下面的方法:
Array.from(string).length
Unicode Code Point 转义
'\u{2003E}'
添加字符串处理函数
-
String.fromCodePoint()
:输入 Unicode Code Point,返回对应字符。 -
String.prototype.codePointAt()
:输入字符,返回对应码点。 -
String.prototype.at()
:返回字符串特定位置的字符。
增强正则表达式
为正则表达式添加 u
修饰符,以正式支持 UTF-16 编码。
/^.$/.test('𠀾') // false
/^.$/u.test('𠀾') // true
Unicode 正规化
有些字符除了字母以外,还有附加符号。比如,汉语拼音的 Ǒ,字母上面的声调就是附加符号。对于许多欧洲语言来说,声调符号是非常重要的。 Uncode 对需要附加符号的情况提供了两种表示方法:
- 带附加符号的单个字符,即使用一个 Code Point 表示一个字符。
- 将附加符号与主体字符复合显示的复合字符,即使用两个 Code Point 表示一个字符。
以 Ǒ 为例:
// 方法 1
'\u01D1' // Ǒ
// 方法 2
'\u004F\u030C' // Ǒ
以上两种表示方法,在视觉和语义上都完全相同,理应等价处理。但,ES 无法辨别。
'\u01D1'==='\u004F\u030C' // false
为了解决问题,引入了 String.prototype.normalize()
方法,以允许 Unicode 正规化(为不同的表示方法生成唯一的 Code Point)。
'\u01D1'.normalize() === '\u004F\u030C'.normalize() // true