Muitas vezes Javascript é motivo de piadas/memes de alguém dizendo que o funcionamento do Javascript é estranho quando compara algumas coisas. Mas em geral, isso é só falta de conhecimento de como a linguagem funciona por baixo dos panos.
E uma desses tipos de comparações que parecem erradas é a comparação de strings.
Comparando strings
Strings
são comparadas caracter por caracter e em ordem alfabética. Algumas regras importantes são:
- Uma letra minúscula é sempre maior que a mesma letra maiúscula:
console.log("a" > "Z"); //true
- Letras com marcas/sinais diacríticas são “fora de ordem”:
console.log("Österreich" > "Zealand"); //true
Marcas diacríticas são sinais gráficos que são adicionados a uma letra ou grifo para indicar um significado específico como a pronúncia ou a diferenciação entre palavras. Cedilha, acento agudo, crase, acento circunflexo são exemplos de diacríticos.
Essa regra das marcas diacríticas pode causar estranhamento se tentarmos por exemplo ordenar uma lista de países em ordem alfabética. A maioria imaginaria que “Zealand” viria depois de “Österreich” o que não é verdade.
Encoded strings
Para entender porque isso acontece precisamos entrar no Javascript de maneira mais profunda, observando que strings são encoded em UTF-16
, ou seja, cada caractere tem um número correspondente. Você pode verificar isso executando str.codePointAt(pos)
:
console.log("Z".codePointAt(0)); //90
console.log("z".codePointAt(0)); //122
Também dá para fazer o caminho inverso, criando um caractere a partir da sua representação numérica:
console.log(String.fromCodePoint(90)); //Z
Se a gente quisesse imprimir todos os caracteres do alfabeto (latino) em uma string o código seria:
let str = "";
for (let i = 65; i <= 220; i++) {
str += String.fromCodePoint(i);
}
console.log(str); // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
// ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ
Se você reparou bem, percebeu que as letras maiúsculas vem antes das minúsculas, e todas elas antes das letras com acentos. É por esse motivo que a > Z
. Os caracteres são comparados por seus códigos numéricos. Quando comparamos strings o caractere “maior” significa que o código daquele caractere é maior do que o outro.
Comparando strings do jeito certo
O algoritmo correto para comparar strings pode ser mais complexo do que parece porque alfabetos são diferentes para idiomas diferentes. Então, o browser precisa saber o idioma que queremos comparar.
Felizmente, browsers modernos suportam internacionalização provendo um método especial para comparar strings em idiomas diferentes seguindo regras.
Chamar str.localeCompare(str2)
retorna um inteiro indicando se str
é menor, igual ou maior que str2
de acordo com as regras do idioma:
- Retorna um número negativo se
str
é menor questr2
. - Retorna um número positivo se
str
é maior questr2
. - Retorna
0
sestr
estr2
são equivalentes.
console.log("Österreich".localeCompare("Zealand")); //-1
Esse método (localeCompare
) tem mais 2 argumentos adicionais, que permitem especificar o idioma (por padrão pego do ambiente, ordem das letras depende do idioma) e configurar regras adicionais como case sensitive ou se ”a”
e ”á”
deveriam ser tratadas como a mesma coisa, etc.