问题
Javascript 有一段时间了Unicode Problem。位于 U+0000...U+FFFF 范围之外的 Unicode 代码点被称为 星体代码点,并且存在问题,因为它们不容易通过正则表达式匹配:
// `?` is an astral symbol because its codepoint value
// of U+1F30D is outside the range U+0000...U+FFFF
// Astral symbols do not work with regular expressions as expected
var regex = /^[bc?]$/;
console.log(
regex.test('a'), // false
regex.test('b'), // true
regex.test('c'), // true
regex.test('?') // false (!)
);
console.log('?'.match(regex)); // null (!)
原因是因为这一个星体代码点实际上是由两部分,或者更准确地说是由两个“代码单元”组成,而这两个代码单元结合在一起来塑造人物。
console.log("\u1F30D") // Doesn't work
console.log("\uD83C\uDF0D") // ?
星体符号?实际上由两个代码单元组成:? = U+D83C + U+DF0D!
所以如果你想匹配这个星体符号,你必须使用下面的正则表达式和匹配器:
var regex = /^([bc]|\uD83C\uDF0D)$/;
console.log(
regex.test('a'), // false
regex.test('b'), // true
regex.test('c'), // true
regex.test('\uD83C\uDF0D') // true
);
console.log('\uD83C\uDF0D'.match(regex)); // { 0: "?", 1: "?", index: 0 ... }
所有星体符号都有这种分解。惊讶吗?好吧,也许您应该这样做——这种情况并不经常发生!它只发生在很少使用的星体代码点。我和世界上其他人使用的大多数代码点都不是星体的——它们在 U+0000...U+FFFF 的范围内——所以我们通常不会看到这个问题。表情符号是这条规则的一个新例外 - 所有表情符号都是星体符号,并且由于社交媒体,它们的使用在世界范围内变得越来越流行。
使用像这样的代码单元是 Unicode 的一个实现细节,不幸的是它暴露给了 Javascript 程序员。由于不清楚是使用字符逐字(?)还是使用代码单元分解(U+D83C + U+DF0D ) 每当使用像 match, test, ... 这样的字符串函数时;或者每当使用正则表达式和字符串文字时。然而,语言设计者和实现者都在努力改进。
解决方案
ECMAScript 6 (ES6) 的最新新增功能是正则表达式匹配 introduction of a u flag。这允许您通过 codepoint 进行匹配,而不是通过 code units 进行匹配(默认)。
var regex = /^[bc?]$/u; // <-- u flag added
console.log(
regex.test('a'), // false
regex.test('b'), // true
regex.test('c'), // true
regex.test('?') // true <-- it now works!
);
通过使用u 标志,您不必担心您的代码点是否是星体代码点,也不必在代码单元之间进行转换。 u 标志使正则表达式以直观的方式工作 - 即使是表情符号!然而,并不是每个版本的 Node.js 和每个浏览器都支持这个新特性。要支持所有环境,您可以使用像 regenerate 这样的库。