javascript - 为什么在ECMAScript 6中,U + D800和U + DBFF之间的代码点会生成一个一字串的字符串?

标签 javascript unicode ecmascript-6 utf-16

我太困惑了。使用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:我想知道为什么上述方法返回长度为1的字符串。由于我的浏览器的ES6实现在字符串中包含UCS-2编码(每个字符代码使用2个字节),因此U + D800不应生成2个长度的字符串吗?

这两种方法都为U + D800代码点返回一个元素的字符串(字符代码:55296,与0xD800相同)。但是,对于大于U + FFFF的代码点,每个代码点都会返回一个包含两个元素的字符串,即前导和尾随。 Lead可能是U + D800和U + DBFF之间的数字,我不确定这一点,我只知道它有助于更​​改结果代码点。对我而言,返回值没有意义,它代表了领先优势。我理解不对吗?

最佳答案

我认为您对Unicode编码的总体工作方式感到困惑,所以让我尝试解释一下。

Unicode本身仅以特定顺序指定一个称为“代码点”的字符列表。它没有告诉您如何将它们转换为位,只是给它们一个介于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.它的工作方式是说“如果您查看一块16位的内存,并且它是介于0xD800和0xDBF之间的数字(“低代理”),那么您还需要查看接下来的16位内存”。任何执行此额外检查的代码段都将其数据处理为UTF-16,而不是UCS-2。

重要的是要了解内存本身并不“知道”它的编码方式,UCS-2和UTF-16之间的区别在于您如何解释该内存。编写软件时,必须选择要使用的解释。

现在,到Javascript ...

Javascript通过将其内部表示解释为UTF-16来处理字符串的输入和输出。太好了,这意味着您可以键入并显示著名的💩字符,该字符不能存储在一个16位内存中。

问题在于大多数内置的字符串函数实际上都以UCS-2的形式处理数据-也就是说,它们一次查看16位,并且不在乎所看到的是否是特殊的“代理”。 function you used, charCodeAt()就是这样的一个示例:它从内存中读取16位,并以0到65535之间的数字的形式提供给您。如果输入💩,它将只给您返回前16位;要求它提供下一个“字符”,它将为您提供第二个16位(这将是“高代理”,介于0xDC00和0xDFFF之间)。

在ECMAScript 6(2015)中,new function was added: codePointAt()。该函数将检查它们是否代表UTF-16代理代码单元之一,而不是仅仅看一下16位并提供给您,如果是,则寻找“另一半”-因此它为您提供0到0之间的数字。 1114111.如果您喂it,它将正确地给您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







您已编辑的问题现在会问到:


  我想知道为什么上述方法返回长度为1的字符串。U+ D800是否应该生成2长度的字符串?


十六进制值D800的十进制值为55296,小于65536,因此考虑到我上面所说的所有内容,它适合16位内存。因此,如果我们要求charCodeAt读取16位内存,并且在其中找到该数字,则不会有问题。

类似地,.length属性测量字符串中有多少组16位。由于此字符串存储在16位内存中,因此没有理由期望除1以外的任何长度。

关于此数字的唯一不寻常之处在于,在Unicode中,该值是保留的-没有而且永远不会有字符U + D800。这是因为它是告诉UTF-16算法“这只是半个字符”的不可思议的数字之一。因此,任何尝试创建此字符串的行为都可能仅仅是错误-像opening a pair of brackets that you never close一样,它是不平衡的,不完整的。

最终以字符串2结束的唯一方法是,如果引擎以某种方式猜测到后半部分应该是什么?但是怎么知道呢?从0xDC00到0xDFFF,共有1024种可能性,可以将其插入上面显示的公式中。因此,它不会猜测,并且由于不会出错,因此您得到的字符串为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));

关于javascript - 为什么在ECMAScript 6中,U + D800和U + DBFF之间的代码点会生成一个一字串的字符串?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42181070/

相关文章:

javascript - 是否可以使用 Wordpress CMS 更改 SCSS 变量?

javascript - 按对象值选择数组索引

android - 使用自定义表情符号创建软键盘

javascript - 结合航点和水平滚动

javascript - 如何让删除按钮在这段代码上起作用

python - 匹配任何语言的字母

JSON 和转义字符

javascript - 使用原生 ES6 Promises 去 Node 化/压缩标准回调

javascript - Material UI 组件破坏 React 应用

javascript - 如何检查对象数组中的缺失值并使用特殊字符添加缺失值