首页 > 解决方案 > 为什么 JavaScript `Intl.Collat​​or.prototype.compare()` 方法会产生与传统 UTF-16 比较特殊字符不同的结果?

问题描述

今天,我偶然发现了一个关于 JavaScript / ECMAScript 国际化 API 的奇怪问题,我在任何地方都找不到合适的解释。/比较两个特定字符 - 正斜杠 ( ) 和下划线 ( ) 字符时,我得到了不同的结果_

  1. 普通香草/传统的基于 UTF-16 的比较
  2. Intl.Collator.prototype.compare()方法_

基于普通/传统 UTF-16 的比较

// Vanilla JavaScript comparator
const cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;

console.log(cmp('/', '_'));
// Output: -1

// When sorting
const result = ['/', '_'].sort(cmp);

console.log(result);
// Output: ['/', '_']

Intl.Collator.prototype.compare()方法_

const collator = new Intl.Collator('en', {
  sensitivity: 'base',
  numeric: true
});

console.log(collator.compare('/', '_'));
// Output: 1

// When sorting
const result = ['/', '_'].sort(collator.compare);

console.log(result);
// Output: ['_', '/']

问题

为什么两种技术会产生不同的结果?这是 ECMAScript 实现中的错误吗?我在这里错过/无法理解什么?是否还有其他此类字符组合会为英语 ( en) 语言/区域设置产生不同的结果?

编辑 2021-10-01

正如@tj-crowder 指出的那样,将所有“ASCII”替换为“UTF-16”。

标签: javascriptnode.jssortinginternationalizationcomparison

解决方案


一般来说

当您在字符串上使用<>时,它们会根据它们的 UTF-16 代码单元值(不是 ASCII,但 ASCII 与许多常见字符的这些值重叠)进行比较。委婉地说,这是有问题的。例如,问法国人是否 "z" < "é"真的应该为真(表示z在 之前é):

console.log("z" < "é"); // true?!?!

当您使用 时Intl.Collator.prototype.compare,它会根据您提供的选项为您的语言环境使用适当的排序规则(松散地排序)。在许多情况下,这可能与UTF-16 代码单元值的结果不同。例如,即使在en语言环境中,也会返回后面Collator更合理的结果:z é

console.log(new Intl.Collator("en").compare("z", "é")); // 1

_特别/

我无法具体告诉您为什么_以及与您正在使用/的语言环境(以及我正在使用的那个)中的 UTF-16 代码单元的顺序不同en,无论是en-US,en-UK还是其他。但发现它们的顺序在 ASCII 和 Unicode 之间有所不同也就不足为奇了。(请记住,UTF-16 代码单元值来自它们的 ASCII 值。_/

ASCII 的顺序是在 1960 年代初精心设计的(有一个PDF详细介绍了它),但除了 AZ 和 0-9 的顺序之外,基本上没有考虑语言顺序。从 1963 年开始使用原始 ASCII。直到 1967 年才被添加到一个可用位置,其数值高于. 可能没有比ASCII更晚/更高(数字上)的原因。/_/_/

Unicode 的排序规则在 1990 年代(一直到今天)经过精心设计,具有不同的目标(包括语言目标)、设计要求和设计约束。据我所知(我不是 Unicode 专家),TR10TR35的第 5 部分描述了 Unicode 的排序规则。_我还没有找到为什么在根排序规则中之前的具体理由/en使用根排序规则)。我确定它就在某个地方。我确实注意到它的一个方面似乎是按类别分组。的类别_是“连接符标点符号”,而 的类别/是“其他标点符号”。也许这与为什么/迟于_.

但基本的答案是:它们不同,因为 ASCII 的排序和 Unicode 排序规则是根据不同的约束和要求设计的。


推荐阅读