【问题标题】:Unicode surrogate pairs and String.fromCodePoint() — JavaScriptUnicode 代理对和 String.fromCodePoint() — JavaScript
【发布时间】:2019-05-21 06:23:44
【问题描述】:

我正在处理原始字符串,其中包含 UTF 星体符号代理部分的转义序列。 (我想我说得对……)

console.log("\uD83D\uDCA9")
// => ????

让我们以上面的表情符号为例。如果我有代理对 (\uD83D\uDCA9) 我怎样才能反过来将它的十六进制值转换为 Javascript 的 String.fromCodePoint() 函数的有效参数?

我尝试了以下方法:

const codePoint = ["D83D", "DCA9"].reduce((acc, cur) => {
    return acc += parseInt(cur, 16);
}, 0);

console.log(String.fromCodePoint(codePoint));
// => ???? (some weird symbol appears, not ????!)

PS:我熟悉 ES6 转义序列,它在括号 {...} 之间显示十六进制值,而不是使用代理一半。 但我需要使用代理对来做到这一点!

非常感谢任何建议。

【问题讨论】:

    标签: javascript unicode hex unicode-escapes surrogate-pairs


    【解决方案1】:

    您可以将 列表 值传递给函数:

    console.log(String.fromCodePoint(0xd83d, 0xdca9));
    

    因此,String.fromCodePoint() 的“有效参数”不一定是单个值,实际上对于需要代理对的字符,根据定义 不能 是单个值。为什么?因为就String.fromCodePoint() 而言,每个单独的数字源值都必须是16 位(2 字节)值。如果您可以传递更大的单个数字,则不需要代理对!

    编辑:以上段落的大部分内容是不准确的; .fromCodePoint() 方法将接受完整的 Unicode 代码点值(大于 16 位)。当然,它仍然必须将它们拆分为代理对,因为 JavaScript 字符串是 UTF-16,但这意味着如果您碰巧有全尺寸的 Unicode 代码点,您不必自己将它们拆分,这很好.但是,如果您确实已经有了对,那么自己将它们组合起来确实没有意义,因为当作为点列表的一部分传递时,该方法也适用于这些对。

    如果数组中有值,可以使用apply 调用函数:

    var points = [0xd83d, 0xdca9];
    console.log(String.fromCodePoint.apply(String, points));
    

    【讨论】:

    • 这是一个非常好的答案。谢谢! Apply() 甚至可以使用由多个表情符号组成的表情符号,例如这个?‍❤️‍?(\uD83D\uDC69\u200D\u2764\uFE0F\u200D\uD83D\uDC69)我仍然对MDN documentation有点困惑,它以这个为例:String.fromCodePoint(0x1D306, 0x61, 0x1D307) // "\uD834\uDF06a\uD834\uDF07"我不知道根本看不到参数和转义序列的相关性。
    • @AudunOlsen 这很有趣,也有点令人惊讶;我会玩弄那个。 编辑 啊,有趣。根据规范和我的 Firefox,函数 将 接受完整的 32 位值(直到最大的实际代码点)。我仍然认为,出于您的目的,明确地使用这些对可能是最容易做的事情,而不是试图将这些对组合成一个值而只是让.fromCodePoint() 再次将它们拆分。我会更新答案。
    【解决方案2】:

    Pointy 的解决方案是正确的,但要回答您的问题,您的公式出了什么问题,问题是您只需添加 0xD83D 和 0xDCA9,从而得到 0x1B4E6。但这不是代理人的工作方式。你应该使用正确的公式

    ( (first - 0xD800) << 10) + (second - 0xDC00) + 0x10000
    

    可以简写为

    (first - 0xD7F7) << 10) + second
    

    见Unicode encodings。

    如果你这样做,你会得到 0x1F4A9。

    const codePoint = ["D83D", "DCA9"].reduce((acc, cur) => {
      cur = parseInt(cur, 16); return acc += cur<0xDC00 ? (cur-0xD7F7)<<10 : cur;
      }, 0);
    
    console.log(String.fromCodePoint(codePoint));
    // => now outputs ?!

    【讨论】:

    • 非常有见地,谢谢!虽然我现在明白我不会理解十六进制。你的等式中有两个新的外国数字(至少对我来说); 0xD7F7 和 0x400。 parseInt(0xD7F7, 10) // =&gt; 55287 和 parseInt(0x400, 10) =&gt; 1024,这些数字有什么关系?我看不到模式。
    • 是的,对不起,我应该更好地解释这部分。第一个字在其低 10 位中包含代码点的第 10 到 19 位,第二个字包含低 10 位,因此您必须屏蔽和移动事物。如果将所有需要的加法和减法加在一起,0xD7F7 就是你得到的。另请参阅我的答案中的链接。
    • @AudunOlsen 它与十六进制无关。您所做的错误也可以用小数显示:假设代理对是 12 和 34(十进制!)。你所做的是,12 + 34 = 46。正确的代码点是 1234。所以公式是 (12 * 10 ^ 2) + 34 = 1234。10 ^ 2 部分是移位操作
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-23
    • 2018-10-15
    • 1970-01-01
    • 2017-08-05
    • 2013-10-17
    • 2011-08-15
    相关资源
    最近更新 更多