String.normalize()的作用是什么?

13

在审查 JavaScript 概念时,我发现了 String.normalize()。这不是出现在 W3School 的“JavaScript字符串参考”中的东西,因此可能是我之前错过它的原因。

我在 HackerRank 上找到了更多关于它的信息,其中写道:

返回包含调用字符串值的 Unicode 标准化形式的字符串。

附带示例:

var s = "HackerRank";
console.log(s.normalize());
console.log(s.normalize("NFKC"));

输出:

HackerRank
HackerRank

GeeksForGeeks 中,还有以下内容:

string.normalize() 是 JavaScript 的一个内置函数,用于返回给定输入字符串的 Unicode 正规化形式。

示例:

<script> 
  
  // Taking a string as input. 
  var a = "GeeksForGeeks"; 
    
  // calling normalize function. 
  b = a.normalize('NFC') 
  c = a.normalize('NFD') 
  d = a.normalize('NFKC') 
  e = a.normalize('NFKD') 
    
  // Printing normalised form. 
  document.write(b +"<br>"); 
  document.write(c +"<br>"); 
  document.write(d +"<br>"); 
  document.write(e); 
    
</script> 

输出结果为:

GeeksForGeeks
GeeksForGeeks
GeeksForGeeks
GeeksForGeeks

也许给出的示例真的很糟糕,因为它们没有让我看到任何变化。

我想知道...这种方法有什么意义呢?


15
首先声明,w3schools.com不是官方参考资料,与W3C无关。这里有一个合适的资源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize - user5734311
1
我知道@ChrisG,但内容通常非常好。 - Tiago Martins Peres
8
不,现在已经不像以前那么糟糕了,但是这个社区尤其还在受苦。我想这个问题的存在就证明了我的观点,对吧? - user5734311
1
String.prototype.normalize() 从技术上讲是正确的,因为 normalize() 是你在实例上调用的动态方法,而不是类本身。 normalize() 的目的是为了能够比较看起来相同但由不同字符组成的字符串,正如在 MDN 上的示例代码中所示。 - user5734311
8
令人困惑的是,有人会为处理Unicode字符串的normalize()函数编写“文档”,却使用纯ASCII字符串进行演示…… - Niet the Dark Absol
显示剩余2条评论
5个回答

6
MDN文档所述,String.prototype.normalize()返回字符串的Unicode规范化形式。这是因为在Unicode中,一些字符可以有不同的表示代码。
以下是示例(摘自MDN):

const name1 = '\u0041\u006d\u00e9\u006c\u0069\u0065';
const name2 = '\u0041\u006d\u0065\u0301\u006c\u0069\u0065';

console.log(`${name1}, ${name2}`);
// expected output: "Amélie, Amélie"
console.log(name1 === name2);
// expected output: false
console.log(name1.length === name2.length);
// expected output: false

const name1NFC = name1.normalize('NFC');
const name2NFC = name2.normalize('NFC');

console.log(`${name1NFC}, ${name2NFC}`);
// expected output: "Amélie, Amélie"
console.log(name1NFC === name2NFC);
// expected output: true
console.log(name1NFC.length === name2NFC.length);
// expected output: true

正如您所看到的,字符串Amélie有两种不同的Unicode表示。通过规范化,我们可以将这两种形式减少为相同的字符串。

6
取决于处理字符串的具体任务:通常情况下,如果只是从用户获取输入并将其返回给用户,则不需要(它);但是要检查/搜索/用作键等这样的字符串,可能需要一种唯一的方法来识别相同的字符串(从语义角度讲)。
主要问题是您可能有两个从语义上讲相同但表示不同的字符串:例如一个带重音符号(一个码点)和一个由字符和重音符号组成的字符(一个码点代表字符,一个码点代表组合重音符号)。用户可能无法控制输入文本的方式,因此可能会有两个不同的用户名或两个不同的密码。但是如果您篡改数据,则可能根据初始字符串而获得不同的结果。用户不喜欢这种情况。
另一个问题涉及组合字符的唯一排序顺序。您可能会有一个带有重音符号和低端部分(例如塞迪利亚)的字符:您可以使用多个组合来表示它:“纯字符,尾巴,重音符号”、“纯字符,重音符号,尾巴”、“字符+尾巴,重音符号”、“字符+重音符号,塞迪利亚”。
还可能出现退化情况(特别是如果您从键盘键入):您可能会获得应该删除的码点(您可能会拥有无限长的字符串,其等效于几个字节)。
无论如何,对于排序字符串,您(或您的库)需要一个标准化的形式:如果已经提供正确的形式,则库将不需要再次转换它。
因此:您希望相同(从语义角度讲)的字符串具有相同的Unicode码点序列。
注意:如果直接在UTF-8上操作,还应关注UTF-8的特殊情况:同一码点可以用不同的方式表示[使用更多字节]。这也可能是安全问题。
“K”通常用于“搜索”和类似任务:CO2和CO₂将以相同的方式进行解释,但这可能会改变文本的含义,因此通常只在内部临时任务中使用,但保留原始文本。

4
很好地解释在这里--> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize 简短回答:关键在于字符是通过编码方案(如ASCII,UTF-8等)表示的(我们主要使用UTF-8),有些字符有多种表示形式。因此,两个字符串可能呈现相似的效果,但它们的Unicode可能不同!因此,字符串比较可能会失败!因此我们使用规范化(normalize)来返回单一类型的表示。
// source from MDN

let string1 = '\u00F1';                           // ñ
let string2 = '\u006E\u0303';                     // ñ

string1 = string1.normalize('NFC');
string2 = string2.normalize('NFC');

console.log(string1 === string2);                 // true
console.log(string1.length);                      // 1
console.log(string2.length);                      // 1

2
字符串的规范化并不仅限于JavaScript - 在Python中也有相关实例,详见此处。参数的有效值由Unicode定义(更多关于Unicode规范化的信息)。
在JavaScript中,需要注意的是,有关String.normalize()String.prototype.normalize()的文档已经存在。正如@ChrisG所提到的,

String.prototype.normalize()在技术上是正确的,因为normalize()是你在实例上调用的动态方法,而不是类本身。normalize()的目的是为了能够比较看起来相同但由不同字符组成的字符串,正如MDN上的示例代码所示。

然后,当涉及到它的使用时,发现了一个非常好的String.normalize()使用示例
let s1 = 'sabiá';
let s2 = 'sabiá';

// one is in NFC, the other in NFD, so they're different
console.log(s1 == s2); // false

// with normalization, they become the same
console.log(s1.normalize('NFC') === s2.normalize('NFC')); // true

// transform string into array of codepoints
function codepoints(s) { return Array.from(s).map(c => c.codePointAt(0).toString(16)); }

// printing the codepoints you can see the difference
console.log(codepoints(s1)); // [ "73", "61", "62", "69", "e1" ]
console.log(codepoints(s2)); // [ "73", "61", "62", "69", "61", "301" ]

因此,在这个例子中,saibásaibá在人眼或者使用console.log()时看起来一样,但是如果没有规范化,我们比较它们的结果会不同。然后,通过分析代码点,我们可以看到它们是不同的。


1
这里已经有一些很好的回答了,但我想举一个实际例子。 我喜欢把翻译圣经当作一种业余爱好。在我能接受(免费)的范围内,市面上提供的卡片式学习工具并没有让我太满意,于是我自己制作了一套。问题是,在Unicode中有不止一种方法可以使用希伯来语和希腊语来得到完全相同的结果。例如:
בָּא
בָּא

这些在您的屏幕上应该看起来完全相同,实际上它们是相同的。然而,第一个是在dagesh(字母中间的点)之前输入qamats(下面的小t形状),而第二个是在qamats之前输入dagesh。现在,由于您只是阅读此内容,您并不关心。您的网络浏览器也不关心。但是当我的闪卡比较这两者时,它们就不同了。对于幕后的代码来说,这与说“center”和“centre”是相同的没有区别。
同样,在希腊语中:
ἀ
ἀ

这两个应该看起来几乎相同,但顶部是一个Unicode字符,第二个是两个Unicode字符。我在我的闪卡中打出哪一个将取决于我坐在哪个键盘前。
当我添加闪卡时,信不信由你,我并不总是打入100个单词的词汇表。这就是为什么上帝给我们电子表格的原因。有时候我导入列表的地方会以一种方式做,有时候会以另一种方式做,有时候会混合使用。但是当我输入时,我并不想记住dagesh或quamats出现的顺序,或者重音是否作为单独的字符键入。无论我是否记得先输入dagesh,我都希望得到正确的答案,因为实际上无论哪种方式,它在实际意义上都是相同的答案。
所以在保存闪卡之前,我对顺序进行了规范化,在检查之前也进行了规范化,结果是无论我以哪种方式输入,它都是正确的!
如果您想查看结果:

https://sthelenskungfu.com/flashcards/

您需要一个谷歌或Facebook账户登录,这样它就可以跟踪进度等。据我所知(或者关心的),目前只有我和我的女儿在使用它。
它是免费的,但永远处于测试阶段。

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