为什么在 ECMAScript 6 中 U+D800 和 U+DBFF 之间的代码点会生成一个长度的字符串?
Why does code points between U+D800 and U+DBFF generate one-length string in ECMAScript 6?
我太糊涂了。为什么在使用 ECMAScript 6 本机 Unicode 助手时,从 U+D800 到 U+DBFF 的代码点编码为单个(2 字节)字符串元素?
我不是在问 JavaScript/ECMAScript 如何本地编码字符串,我是在问使用 UCS-2 编码 UTF-16 的额外功能。
var str1 = '\u{D800}';
var str2 = String.fromCodePoint(0xD800);
console.log(
str1.length, str1.charCodeAt(0), str1.charCodeAt(1)
);
console.log(
str2.length, str2.charCodeAt(0), str2.charCodeAt(1)
);
Re-TL;DR: 我想知道为什么上面的方法是 return 一个长度为 1
的字符串。 U+D800 不应该生成 2
长度的字符串吗,因为我的浏览器的 ES6 实现在字符串中合并了 UCS-2 编码,每个字符代码使用 2 个字节?
这两种方法 return U+D800 代码点的单元素字符串(字符代码:55296
,与 0xD800
相同)。但是对于大于 U+FFFF 的代码点,每个 return 都是一个二元素字符串,前导和尾部。 lead 将是 U+D800 和 U+DBFF 之间的数字,trail 我不确定,我只知道它有助于更改结果代码点。对我来说 return 值没有意义,它代表了一个没有踪迹的线索。我理解错了吗?
嗯,它这样做是因为规范规定它必须:
- http://www.ecma-international.org/ecma-262/6.0/#sec-string.fromcodepoint
- http://www.ecma-international.org/ecma-262/6.0/#sec-utf16encoding
这两个一起说,如果参数是 < 0
或 > 0x10FFFF
,则会抛出 RangeError
,否则任何代码点 <= 65535
都会合并到结果字符串中as-is.
至于为什么要这样规定,我就不知道了。似乎 JavaScript 并不真正支持 Unicode,只支持 UCS-2。
Unicode.org 对此事有如下说法:
http://www.unicode.org/faq/utf_bom.html#utf16-2
Q: What are surrogates?
A: Surrogates are code points from two special ranges of Unicode values, reserved for use as the leading, and trailing values of paired code units in UTF-16. Leading, also called high, surrogates are from D80016 to DBFF16, and trailing, or low, surrogates are from DC0016 to DFFF16. They are called surrogates, since they do not represent characters directly, but only as a pair.
http://www.unicode.org/faq/utf_bom.html#utf16-7
Q: Are there any 16-bit values that are invalid?
A: Unpaired surrogates are invalid in UTFs. These include any value in the range D80016 to DBFF16 not followed by a value in the range DC0016 to DFFF16, or any value in the range DC0016 to DFFF16 not preceded by a value in the range D80016 to DBFF16.
因此 String.fromCodePoint
的结果并不总是有效的 UTF-16,因为它可以发出不成对的代理。
我认为您对 Unicode 编码的一般工作方式感到困惑,所以让我试着解释一下。
Unicode 本身只是按特定顺序指定一个字符列表,称为 "code points"。它不会告诉您如何将它们转换为位,它只是给它们一个介于 0 和 1114111 之间的数字(十六进制,0x10FFFF)。这些从 U+0 到 U+10FFFF 的数字可以用几种不同的方式表示为位。
在早期版本中,预计 0 到 65535 (0xFFFF) 的范围就足够了。这可以自然地用 16 位表示,使用与无符号整数相同的约定。这是存储 Unicode 的原始方式,现在称为 UCS-2。要存储单个代码点,您需要保留 16 位内存。
后来觉得这个范围不够大;这意味着存在高于 65535 的代码点,您无法在 16 位内存中表示这些代码点。 UTF-16 was invented as a clever way of storing these higher code points. 它通过说 "if you look at a 16-bit piece of memory, and it's a number between 0xD800 and 0xDBF (a "low surrogate") 来工作,那么你还需要查看接下来的 16 位内存"。任何执行此额外检查的代码都将其数据处理为 UTF-16,而不是 UCS-2。
重要的是要了解内存本身并不 "know" 它采用哪种编码,UCS-2 和 UTF-16 之间的区别在于 你如何解释内存.当你写一个软件时,你必须选择你要使用的解释。
现在,进入 Javascript...
Javascript 通过将其内部表示解释为 UTF-16 来处理字符串的输入和输出。太棒了,这意味着你可以输入并显示一个16位内存无法存储的名字。
问题是大多数内置字符串函数实际上将数据作为 UCS-2 处理——也就是说,它们一次查看 16 位,并且不在乎它们看到的是否是一个特殊的 "surrogate"。 function you used, charCodeAt()
就是这样的一个例子:它从内存中读取 16 位,并将它们作为 0 到 65535 之间的数字提供给你。如果你喂它,它只会返回前 16 位;之后询问下一个 "character",它会给你第二个 16 位(这将是一个 "high surrogate",在 0xDC00 和 0xDFFF 之间)。
在 ECMAScript 6 (2015) 中,new function was added: codePointAt()
。此函数不是只查看 16 位并将它们提供给您,而是检查它们是否代表 UTF-16 代理代码单元之一,如果是,则查找 "other half" - 因此它会为您提供一个介于 0 之间的数字和 1114111。如果你喂它,它会正确地给你 128169。
var poop = '';
console.log('Treat it as UCS-2, two 16-bit numbers: ' + poop.charCodeAt(0) + ' and ' + poop.charCodeAt(1));
console.log('Treat it as UTF-16, one value cleverly encoded in 32 bits: ' + poop.codePointAt(0));
// The surrogates are 55357 and 56489, which encode 128169 as follows:
// 0x010000 + ((55357 - 0xD800) << 10) + (56489 - 0xDC00) = 128169
您编辑的问题现在是这样问的:
I want to know why the above approaches return a string of length 1. Shouldn't U+D800 generate a 2 length string?
十六进制值 D800 的十进制为 55296,小于 65536,所以根据我上面所说的一切,这适合 16 位内存。所以如果我们要求 charCodeAt
读取 16 位内存,并且它在那里找到那个数字,它不会有问题。
类似地,.length
属性 测量字符串中有多少组 16 位。由于此字符串存储在 16 位内存中,因此没有理由期望除 1 以外的任何长度。
这个数字唯一不寻常的地方是,在 Unicode 中,该值是 保留的 - 没有,也永远不会有字符 U+D800。那是因为它是告诉 UTF-16 算法 "this is only half a character" 的神奇数字之一。因此 可能的 行为对于创建此字符串的任何尝试都只是一个错误 - 就像 opening a pair of brackets that you never close,它是不平衡的,不完整的。
如果引擎以某种方式猜测后半部分应该是什么,那么您最终得到长度为 2 的字符串的唯一方法是;但它怎么知道呢?有 1024 种可能性,从 0xDC00 到 0xDFFF,可以代入我上面显示的公式。所以它不会猜测,因为它不会出错,所以你得到的字符串是 16 位长。
当然,你可以提供匹配的一半,codePointAt
会为你解释。
// Set up two 16-bit pieces of memory
var high=String.fromCharCode(55357), low=String.fromCharCode(56489);
// Note: String.fromCodePoint will give the same answer
// Glue them together (this + is string concatenation, not number addition)
var poop = high + low;
// Read out the memory as UTF-16
console.log(poop);
console.log(poop.codePointAt(0));
我太糊涂了。为什么在使用 ECMAScript 6 本机 Unicode 助手时,从 U+D800 到 U+DBFF 的代码点编码为单个(2 字节)字符串元素?
我不是在问 JavaScript/ECMAScript 如何本地编码字符串,我是在问使用 UCS-2 编码 UTF-16 的额外功能。
var str1 = '\u{D800}';
var str2 = String.fromCodePoint(0xD800);
console.log(
str1.length, str1.charCodeAt(0), str1.charCodeAt(1)
);
console.log(
str2.length, str2.charCodeAt(0), str2.charCodeAt(1)
);
Re-TL;DR: 我想知道为什么上面的方法是 return 一个长度为 1
的字符串。 U+D800 不应该生成 2
长度的字符串吗,因为我的浏览器的 ES6 实现在字符串中合并了 UCS-2 编码,每个字符代码使用 2 个字节?
这两种方法 return U+D800 代码点的单元素字符串(字符代码:55296
,与 0xD800
相同)。但是对于大于 U+FFFF 的代码点,每个 return 都是一个二元素字符串,前导和尾部。 lead 将是 U+D800 和 U+DBFF 之间的数字,trail 我不确定,我只知道它有助于更改结果代码点。对我来说 return 值没有意义,它代表了一个没有踪迹的线索。我理解错了吗?
嗯,它这样做是因为规范规定它必须:
- http://www.ecma-international.org/ecma-262/6.0/#sec-string.fromcodepoint
- http://www.ecma-international.org/ecma-262/6.0/#sec-utf16encoding
这两个一起说,如果参数是 < 0
或 > 0x10FFFF
,则会抛出 RangeError
,否则任何代码点 <= 65535
都会合并到结果字符串中as-is.
至于为什么要这样规定,我就不知道了。似乎 JavaScript 并不真正支持 Unicode,只支持 UCS-2。
Unicode.org 对此事有如下说法:
http://www.unicode.org/faq/utf_bom.html#utf16-2
Q: What are surrogates?
A: Surrogates are code points from two special ranges of Unicode values, reserved for use as the leading, and trailing values of paired code units in UTF-16. Leading, also called high, surrogates are from D80016 to DBFF16, and trailing, or low, surrogates are from DC0016 to DFFF16. They are called surrogates, since they do not represent characters directly, but only as a pair.
http://www.unicode.org/faq/utf_bom.html#utf16-7
Q: Are there any 16-bit values that are invalid?
A: Unpaired surrogates are invalid in UTFs. These include any value in the range D80016 to DBFF16 not followed by a value in the range DC0016 to DFFF16, or any value in the range DC0016 to DFFF16 not preceded by a value in the range D80016 to DBFF16.
因此 String.fromCodePoint
的结果并不总是有效的 UTF-16,因为它可以发出不成对的代理。
我认为您对 Unicode 编码的一般工作方式感到困惑,所以让我试着解释一下。
Unicode 本身只是按特定顺序指定一个字符列表,称为 "code points"。它不会告诉您如何将它们转换为位,它只是给它们一个介于 0 和 1114111 之间的数字(十六进制,0x10FFFF)。这些从 U+0 到 U+10FFFF 的数字可以用几种不同的方式表示为位。
在早期版本中,预计 0 到 65535 (0xFFFF) 的范围就足够了。这可以自然地用 16 位表示,使用与无符号整数相同的约定。这是存储 Unicode 的原始方式,现在称为 UCS-2。要存储单个代码点,您需要保留 16 位内存。
后来觉得这个范围不够大;这意味着存在高于 65535 的代码点,您无法在 16 位内存中表示这些代码点。 UTF-16 was invented as a clever way of storing these higher code points. 它通过说 "if you look at a 16-bit piece of memory, and it's a number between 0xD800 and 0xDBF (a "low surrogate") 来工作,那么你还需要查看接下来的 16 位内存"。任何执行此额外检查的代码都将其数据处理为 UTF-16,而不是 UCS-2。
重要的是要了解内存本身并不 "know" 它采用哪种编码,UCS-2 和 UTF-16 之间的区别在于 你如何解释内存.当你写一个软件时,你必须选择你要使用的解释。
现在,进入 Javascript...
Javascript 通过将其内部表示解释为 UTF-16 来处理字符串的输入和输出。太棒了,这意味着你可以输入并显示一个16位内存无法存储的名字。
问题是大多数内置字符串函数实际上将数据作为 UCS-2 处理——也就是说,它们一次查看 16 位,并且不在乎它们看到的是否是一个特殊的 "surrogate"。 function you used, charCodeAt()
就是这样的一个例子:它从内存中读取 16 位,并将它们作为 0 到 65535 之间的数字提供给你。如果你喂它,它只会返回前 16 位;之后询问下一个 "character",它会给你第二个 16 位(这将是一个 "high surrogate",在 0xDC00 和 0xDFFF 之间)。
在 ECMAScript 6 (2015) 中,new function was added: codePointAt()
。此函数不是只查看 16 位并将它们提供给您,而是检查它们是否代表 UTF-16 代理代码单元之一,如果是,则查找 "other half" - 因此它会为您提供一个介于 0 之间的数字和 1114111。如果你喂它,它会正确地给你 128169。
var poop = '';
console.log('Treat it as UCS-2, two 16-bit numbers: ' + poop.charCodeAt(0) + ' and ' + poop.charCodeAt(1));
console.log('Treat it as UTF-16, one value cleverly encoded in 32 bits: ' + poop.codePointAt(0));
// The surrogates are 55357 and 56489, which encode 128169 as follows:
// 0x010000 + ((55357 - 0xD800) << 10) + (56489 - 0xDC00) = 128169
您编辑的问题现在是这样问的:
I want to know why the above approaches return a string of length 1. Shouldn't U+D800 generate a 2 length string?
十六进制值 D800 的十进制为 55296,小于 65536,所以根据我上面所说的一切,这适合 16 位内存。所以如果我们要求 charCodeAt
读取 16 位内存,并且它在那里找到那个数字,它不会有问题。
类似地,.length
属性 测量字符串中有多少组 16 位。由于此字符串存储在 16 位内存中,因此没有理由期望除 1 以外的任何长度。
这个数字唯一不寻常的地方是,在 Unicode 中,该值是 保留的 - 没有,也永远不会有字符 U+D800。那是因为它是告诉 UTF-16 算法 "this is only half a character" 的神奇数字之一。因此 可能的 行为对于创建此字符串的任何尝试都只是一个错误 - 就像 opening a pair of brackets that you never close,它是不平衡的,不完整的。
如果引擎以某种方式猜测后半部分应该是什么,那么您最终得到长度为 2 的字符串的唯一方法是;但它怎么知道呢?有 1024 种可能性,从 0xDC00 到 0xDFFF,可以代入我上面显示的公式。所以它不会猜测,因为它不会出错,所以你得到的字符串是 16 位长。
当然,你可以提供匹配的一半,codePointAt
会为你解释。
// Set up two 16-bit pieces of memory
var high=String.fromCharCode(55357), low=String.fromCharCode(56489);
// Note: String.fromCodePoint will give the same answer
// Glue them together (this + is string concatenation, not number addition)
var poop = high + low;
// Read out the memory as UTF-16
console.log(poop);
console.log(poop.codePointAt(0));