我太困惑了。当使用ECMAScript 6本机Unicode助手时,为什么从U + D800到U + DBFF的代码点编码为单个(2字节)String元素?
我不会问JavaScript / ECMAScript如何本地编码Strings,我问的是使用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)
);

重新 TL; DR:我想知道为什么上述方法返回长度为1
的字符串。不应该U + D800生成2
长度字符串,因为我的浏览器的ES6实现在字符串中包含UCS-2编码,每个字符代码使用2个字节?
这两种方法都返回U + D800代码点的单元素字符串(字符代码:55296
,与0xD800
相同)。但是对于大于U + FFFF的代码点,每个返回一个两元素字符串,即前导和跟踪。 lead是U + D800和U + DBFF之间的数字,而且我不确定,我只知道它有助于更改结果代码点。对我来说,回报值没有意义,它代表了一条没有踪迹的领先者。我理解错了吗?
答案 0 :(得分:3)
我认为你的困惑是关于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()
。这个函数不是只看16位并给你,而是检查它们是否代表UTF-16代理代码单元之一,如果是,则查找“另一半” - 所以它给你一个介于0和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
您编辑的问题现在问:
我想知道为什么上面的方法会返回一个长度为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));
答案 1 :(得分:1)
嗯,这样做是因为规范说它必须:
这两个人一起说如果参数是< 0
或> 0x10FFFF
,则抛出RangeError
,否则任何代码点<= 65535
都会被合并到结果字符串中 - 是
至于为何以这种方式指定事物,我不知道。似乎JavaScript并不真正支持Unicode,只有UCS-2。
Unicode.org在此事上有以下说法:
http://www.unicode.org/faq/utf_bom.html#utf16-2
问:什么是代理?
答:代理是来自两个特殊范围的Unicode值的代码点,保留用作UTF-16中成对代码单元的前导和尾随值。领先的,也称为高代理,从D800 16 到DBFF 16 ,尾随或低代理从DC00 16 到DFFF <子> 16 子>。它们被称为代理,因为它们不直接表示字符,而只是作为一对字符。
http://www.unicode.org/faq/utf_bom.html#utf16-7
问:是否有任何无效的16位值?
答:未配对的代理人在UTF中无效。这些值包括D800 16 到DBFF 16 范围内的任何值,后面没有DC00 16 到DFFF 16 <范围内的值/ sub>,或DC00 16 到DFFF 16 范围内的任何值,前面没有D800 16 到DBFF 16 子>
因此String.fromCodePoint
的结果并不总是有效的UTF-16,因为它可以发出不成对的代理。