JavaScript中从charcode转换为Unicode字符(charcodes > 0xFFFF)

14

我需要从 Unicode 字符编码中获取一个字符串/字符,并使用客户端 JavaScript 将其放入 DOM TextNode 中,以添加到 HTML 页面中。

目前,我的做法是:

String.fromCharCode(parseInt(charcode, 16));

charcode 是包含字符编码的十六进制字符串时,例如"1D400"。应返回的Unicode字符是,但实际上返回了!在16位范围内的字符(0000 ... FFFF)按预期返回。

有任何解释和/或更正建议吗?

谢谢!


3
以下是详细解释:http://mathiasbynens.be/notes/javascript-encoding - Mathias Bynens
4个回答

20

String.fromCharCode只能处理BMP(即最大到U+FFFF)内的码位。要处理更高的码位,可以使用来自Mozilla开发者网络的此函数返回代理对表示:

function fixedFromCharCode (codePt) {
    if (codePt > 0xFFFF) {
        codePt -= 0x10000;
        return String.fromCharCode(0xD800 + (codePt >> 10), 0xDC00 + (codePt & 0x3FF));
    } else {
        return String.fromCharCode(codePt);
    }
}

所以 JScript 字符串是 UTF-16 编码的,而这段代码是一个字符编码 => UTF-16 转换,就我所知... 我预期问题(和解决方案)应该是这样的。它奏效了!谢谢! - leemes
我尝试过这个,但是出现了“字符转换错误”的提示 - 但是我意识到脚本文件是以utf-8编码的;当我将编码更改为ucs2(使用notepad ++)时,它就可以工作了。 - bgmCoder

15

问题在于JavaScript中的字符 (大多数情况下)是UCS-2编码,但可以使用UTF-16代理对在JavaScript中表示超出基本多文种平面的字符。

以下函数改编自将带有破折号字符的punycode转换为Unicode

function utf16Encode(input) {
    var output = [], i = 0, len = input.length, value;
    while (i < len) {
        value = input[i++];
        if ( (value & 0xF800) === 0xD800 ) {
            throw new RangeError("UTF-16(encode): Illegal UTF-16 value");
        }
        if (value > 0xFFFF) {
            value -= 0x10000;
            output.push(String.fromCharCode(((value >>>10) & 0x3FF) | 0xD800));
            value = 0xDC00 | (value & 0x3FF);
        }
        output.push(String.fromCharCode(value));
    }
    return output.join("");
}

alert( utf16Encode([0x1D400]) );

尽管我使用了Anomie的(更短)代码,但我接受了你的解决方案,因为你的代码进行了良好的错误检查(但我不需要它)。 - leemes
请注意,正确的术语只是“UTF-16”编码。这将一对一地映射到前65536个字符的“UCS-2”,除了代理项。但从您的代码中我们可以看出,它只是“普通”的“UTF-16”。 - Alexis Wilke
@AlexisWilke:不完全正确。JavaScript 字符并没有真正暴露为 UCS-2 或 UTF-16:它与 UCS-2 相同,只是允许代理项。它不是 UTF-16,因为允许未匹配的代理项和顺序错误的代理项。只有在浏览器中呈现字符时,UTF-16 风格的代理项才会组合成单个 Unicode 字符。这是一篇很好的背景文章:https://mathiasbynens.be/notes/javascript-encoding - Tim Down

9

根据EcmaScript语言规范的第8.4节,当一个字符串包含实际文本数据时,每个元素都被视为单个UTF-16代码单元。无论这是否是字符串的实际存储格式,字符串中的字符按其初始代码单元元素位置编号,就好像它们使用UTF-16表示。除非另有说明,所有对字符串的操作(不包括其他情况)都将它们视为未区分的16位无符号整数序列;它们不确保生成的字符串处于标准化形式,也不确保产生与语言相关的结果。

因此,您需要将补充码点编码为UTF-16代码单元对。

Java平台上的“补充字符”一文对如何执行此操作进行了良好的描述。

UTF-16使用一个或两个无符号的16位代码单元序列来编码Unicode代码点。值U+0000到U+FFFF在一个16位单元中使用相同的值进行编码。补充字符使用两个代码单元进行编码,第一个来自高代理项范围(U+D800至U+DBFF),第二个来自低代理项范围(U+DC00至U+DFFF)。这可能在概念上与多字节编码类似,但有一个重要的区别:值U+D800到U+DFFF保留供UTF-16使用;没有将它们分配给作为代码点的字符。这意味着软件可以告诉字符串中每个单独的代码单元是表示一个单元字符还是它是两个单元字符的第一个或第二个单元。这比某些传统的多字节字符编码有了显著改进,其中字节值0x41可能意味着字母“A”,也可能是两字节字符的第二个字节。

以下表格显示了一些字符的不同表示形式:

代码点/ UTF-16代码单元

U+0041 / 0041

U+00DF / 00DF

U+6771 / 6771

U+10400 / D801 DC00

一旦您知道UTF-16代码单元,就可以使用javascript函数String.fromCharCode创建字符串:

String.fromCharCode(0xd801, 0xdc00) === ''

@leemes,由于我在引用规范:“15.5.3.2 String.fromCharCode([char0 [,char1 [,...]]])返回一个字符串值,其中包含与参数数量相同的字符。每个参数指定结果字符串的一个字符,第一个参数指定第一个字符,依此类推,从左到右。通过应用操作ToUint16(9.7)将参数转换为字符,并将生成的16位整数视为字符的代码单元值。如果没有提供参数,则结果为空字符串。” - Mike Samuel
@leemes,由于字符是UTF-16代码单元,并且ToUint16(0x10000)=== 0,因此尝试将补充代码单元传递给String.fromCharCode将无法按预期工作。不幸的是,String.fromCharCode(0x10000)=== '\u0000'。Nebosja Ciric和其他人正在努力使下一个版本在i18n方面更好:https://mail.mozilla.org/pipermail/es-discuss/2010-June/011380.html - Mike Samuel
感谢您提供这么详细的解释!它让我更深入地理解了JScript字符串的行为。看起来在w3schools的以下文档中,fromCharCode的描述是错误的,因为它只说“Unicode值”,但0x1A000也是“Unicode值”:W3Schools: fromCharCode() - leemes
我所说的“错误”是指w3schools的描述,而不是你的引用。现在我清楚了,因为我知道String.fromCharCode并不接受任何Unicode字符编码(“码点”),而是接受一个16位代码,表示UTF-16编码(“UTF-16代码单元”),这当然是不同的。谢谢。 - leemes

2

String.fromCodePoint() 也可以解决问题。请参阅此处

最初的回答:使用String.fromCharCode()方法。
console.log(String.fromCodePoint(0x1D622, 0x1D623, 0x1D624, 0x1D400));

输出:



网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接