JavaScript 的内部字符编码:UCS-2 还是 UTF-16?
JavaScript 使用 UCS-2 还是 UTF-16 编码? 由于我在任何地方都找不到这个问题的明确答案,所以我决定调查一下。 答案取决于我们指的是什么:JavaScript 引擎,或语言级别的 JavaScript。
让我们从基础开始……
臭名昭著的BMP
Unicode 通过明确的名称和称为其代码点的整数来识别字符。 例如,©
字符被命名为“版权标志”,其代码点为 U+00A9——0xA9 可以写成十进制的 169。
Unicode 代码空间分为 17 个平面,每个平面有 2^16
(65,536) 个代码点。 这些代码点中的一些尚未分配字符值,一些保留供私人使用,还有一些永久保留为非字符。 每个平面中的代码点具有 xy0000 到 xyFFFF 的十六进制值,其中 xy 是从 00 到 10 的十六进制值,表示这些值属于哪个平面。
第一个平面(xy 为 00)称为基本多语言平面或 BMP。 它包含从 U+0000 到 U+FFFF 的代码点,这些是最常用的字符。
其他十六个位面 (U+010000 → U+10FFFF) 称为补充位面或星体位面。 我不会在这里讨论它们; 请记住,有 BMP 字符和非 BMP 字符,后者也称为增补字符或星体字符。
UCS-2 和 UTF-16 之间的区别
UCS-2 和 UTF-16 都是 Unicode 的字符编码。
UCS-2(2-byte Universal Character Set)通过简单地使用代码点作为 16 位代码单元来产生固定长度的格式。 对于 0 到 0xFFFF 范围内的大多数代码点(即 BMP),这会产生与 UTF-16
完全相同的结果。
UTF-16(16 位 Unicode 转换格式)是 UCS-2 的扩展,允许表示 BMP 之外的代码点。 它为每个代码点生成一个或两个 16 位代码单元的可变长度结果。 这样,它可以对 0 到 0x10FFFF 范围内的代码点进行编码。
例如,在 UCS-2 和 UTF-16 中,BMP 字符 U+00A9 版权符号 (©
) 都被编码为 0x00A9。
代理对
BMP 之外的字符,例如 U+1D306 tetragram for center (𝌆
),只能使用两个 16 位代码单元以 UTF-16 编码:0xD834 0xDF06。 这称为代理对。 请注意,代理对仅表示单个字符。
代理对的第一个代码单元始终在 0xD800 到 0xDBFF 的范围内,称为高代理或前导代理。
代理对的第二个代码单元总是在 0xDC00 到 0xDFFF 的范围内,称为低代理或尾代理。
UCS-2 缺少代理对的概念,因此将 0xD834 0xDF06(以前的 UTF-16 编码)解释为两个单独的字符。
在代码点和代理对之间转换
Unicode 标准 3.0 的第 3.7 节定义了在代理对之间进行转换的算法。
根据以下公式,大于 0xFFFF 的代码点 C 对应于代理项对 <H, L>
:
H = Math.floor((C - 0x10000) / 0x400) + 0xD800
L = (C - 0x10000) % 0x400 + 0xDC00
反向映射,即从代理项对 <H, L>
到 Unicode 代码点 C,由下式给出:
C = (H - 0xD800) * 0x400 + L - 0xDC00 + 0x10000
好的,那么 JavaScript 呢?
ECMAScript 是 JavaScript 的标准化版本,它定义了应该如何解释字符:
符合本国际标准的实施应解释符合 Unicode 标准 3.0 版或更高版本和 ISO/IEC 10646-1 的字符,采用
UCS-2
或UTF-16
作为采用的编码形式,实施级别 3。如果采用 ISO/IEC 10646-1子集没有特别说明,假定为BMP子集,集合300。如果没有特别说明采用的编码形式,则假定为UTF-16编码形式。
换句话说,允许 JavaScript 引擎使用 UCS-2 或 UTF-16。
但是,无论引擎的内部编码如何,规范的特定部分都需要一些 UTF-16 知识。
当然,内部引擎细节对普通 JavaScript 开发人员来说并不重要。 更有趣的是 JavaScript 认为什么是“字符”,以及它如何公开这些字符:
在本文档的其余部分,短语代码单元和单词字符将用于指代用于表示单个 16 位文本单元的 16 位无符号值。 短语 Unicode 字符将用于指代由单个 Unicode 标量值(可能长于 16 位,因此可能由多个代码单元表示)表示的抽象语言或印刷单位。 短语代码点指的是这样一个 Unicode 标量值。 Unicode 字符仅指由单个 Unicode 标量值表示的实体:组合字符序列的组件仍然是单独的“Unicode 字符”,即使用户可能将整个序列视为单个字符。
JavaScript 将代码单元视为单个字符,而人类通常根据 Unicode 字符来思考。 这对 BMP 之外的 Unicode 字符有一些不幸的后果。 由于代理对由两个代码单元组成,'𝌆'.length == 2
,即使那里只有一个 Unicode 字符。 各个代理项的一半被暴露,就好像它们是字符一样:'𝌆' == '\uD834\uDF06'
。
让你想起了什么? 它应该,因为这几乎正是 UCS-2 的工作方式。 (唯一的区别是,从技术上讲,UCS-2 不允许代理字符,而 JavaScript 字符串允许。)
我们可能会争辩说它类似于 UTF-16,除了允许不匹配的代理项一半、错误顺序的代理项是允许的,并且代理项的一半作为单独的字符公开。 我想您会同意将此行为视为“带代理的 UCS-2”更容易。
这种类似于 UCS-2 的行为会影响整个语言——例如,与支持 UTF-16 的语言相比,增补字符范围的正则表达式更难编写。
代理项对仅在浏览器显示时(在布局期间)才重新组合为单个 Unicode 字符。 这发生在 JavaScript 引擎之外。 为了演示这一点,我们可以在单独的 document.write()
调用中写出高代理项和低代理项:document.write('\uD834'); document.write('\uDF06');
。 这最终被渲染为𝌆
——一个字形。
总结
JavaScript 引擎可以在内部自由使用 UCS-2 或 UTF-16。 我所知道的大多数引擎都使用 UTF-16,但无论他们做出什么选择,它只是一个不会影响语言特性的实现细节。
然而,ECMAScript/JavaScript 语言本身根据 UCS-2 而非 UTF-16 公开字符。
如果我们需要转义 Unicode 字符,在必要时将其分成代理项的两半,请随时使用我的 JavaScript 转义工具。
如果你想计算 JavaScript 字符串中 Unicode 字符的数量,或者创建一个基于非 BMP Unicode 代码点的字符串,你可以使用 Punycode.js 的实用函数在 UCS-2 字符串和 UTF-16 代码点之间进行转换 :
// `String.length` replacement that only counts full Unicode characters
punycode.ucs2.decode('𝌆').length; // 1
// `String.fromCharCode` replacement that doesn’t make you enter the surrogate halves separately
punycode.ucs2.encode([0x1D306]); // '𝌆'
punycode.ucs2.encode([119558]); // '𝌆'
ECMAScript 6 将支持一种新的字符串转义序列,即 Unicode 代码点转义,例如 \u{1D306}
。 此外,它将定义 String.fromCodePoint
和 String#codePointAt
,它们都接受代码点而不是代码单元。
相关文章
使用 CSS 和 JavaScript 制作文本闪烁
发布时间:2023/04/28 浏览次数:146 分类:CSS
-
本文提供了使用 CSS、JavaScript 和 jQuery 使文本闪烁的详细说明。
在 PHP 变量中存储 Div Id 并将其传递给 JavaScript
发布时间:2023/03/29 浏览次数:69 分类:PHP
-
本文教导将 div id 存储在 PHP 变量中并将其传递给 JavaScript 代码。
在 JavaScript 中从字符串中获取第一个字符
发布时间:2023/03/24 浏览次数:93 分类:JavaScript
-
在本文中,我们将看到如何使用 JavaScript 中的内置方法获取字符串的第一个字符。
在 JavaScript 中获取字符串的最后一个字符
发布时间:2023/03/24 浏览次数:141 分类:JavaScript
-
本教程展示了在 javascript 中获取字符串最后一个字符的方法