Ontem, dia 05/07/2018, quinta-feira, aconteceu no Hotel Renaissance a segunda edição do JS Experience, o maior evento de JavaScript da cidade de São Paulo. O evento – organizado pelo competente e experiente time do iMasters – contou com uma série de palestras incríveis e eu tive a felicidade de poder participar do quadro do 7Masters que ocorreu no evento.
Em resumo, o 7Masters é um evento que acontece todo mês, onde um tema é escolhido e sete profissionais que trabalham com esta tecnologia são convidados para compartilhar sua experiência em uma palestra de sete minutos. É isso mesmo, apenas sete minutos.
Eu fui o primeiro a me apresentar e o tema escolhido foi “Nada é por acaso, nem os tipos no JS”. Para quem quiser os slides, estão disponíveis aqui.
Tudo tem uma explicação
Como já estamos cansados de saber, o JavaScript é uma linguagem de tipagem dinâmica. Isso significa que a sua engine consegue interpretar qual é tipo do dado que estamos passando sem a necessidade de especificar. Por exemplo, podemos ter uma mesma variável valor que pode assumir diversos tipos:
var valor = 1; // numero valor = "1"; // string valor = {}; // objeto valor = []; // array valor = () => {}; // função // ... etc
E com isso, conseguimos fazer vários tipos de operações, como: soma, subtração, concatenação, comparação, atribuição. Até aí, sem segredos, certo? O “problema” começa quando tentamos fazer operações com tipos diferentes:
1 + '2'; // '3' [] + []; // '' true - true; // 0 [] + {}; // "[object Object]" [] == 0; // true 91 - "1"; // 90
Estes resultados inusitados acabam sendo surpresas (na maior parte das vezes desagradáveis). No entanto, ao contrário do ditado popular de que o JavaScript é maluco, há uma razão lógica para que tudo isso aconteça. Para entender o que acontece, é necessário entender quatro conceitos:
- Precedência
- Associatividade
- Coerção
- Semelhança e Igualdade
Um caso muito simples em que isso pode ser aplicado é no seguinte código:
var b = 3 < 2 < 1; console.log(b); // ?
Você sabe qual é o resultado desta expressão? Se você acha que é false, tenho más notícias: o resultado é true.
Engraçado, né? Mas veja só como a explicação é simples: para realizar esta operação, o JavaScript segue uma tabela de precedência e associatividade (que pode ser encontrada no MDN através deste link). Vamos procurar pelo operador “menos que” (less than).
Note as colunas da tabela. A primeira indica o grau de precedência, ou seja, quanto maior o número, maior é a precedência (ex: a adição precede a igualdade, visto que sua precedência é 13, enquanto a da igualdade é 10). A terceira coluna indica a associatividade. Em termos simples, é a ordem em que deve ser realizada a operação no caso de duas de mesmo nível de precedência.
No nosso caso, temos dois operadores iguais, logo, precisamos realizar na ordem left-to-right (esquerda para direita). Com isso, temos:
false < 1
Nesta etapa, entra o conceito de coerção de tipos. Como temos dois tipos distintos nos dois lados da balança, o JavaScript faz com que ambos estejam em um plano comum. Neste caso, o false é convertido para número com a função Number().
Number(false) < 1
Depois disso, o resultado da operação é bem intuitivo:
Number(false) < 1 0 < 1 true
Fez sentido? O mesmo equivale para quando somamos uma string e um número, por exemplo:
'1' + 2; '1' + String(2); '1' + '2'; // 12
E assim por diante. Para facilitar a nossa vida, o MDN também possui uma tabela de comparações entre os tipos para que possamos ter uma referência (você pode acessar através deste link).
E acredite ou não, estes conceitos são o suficiente para entender como o resultado da expressão abaixo resulta na string “fail”:
(![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]] // “fail”
Se você quiser mais resultados engraçados, dê uma olhada no WTFJS.
Conclusão
Para evitar confusões no código, há algumas precauções que podemos tomar:
- Consultem as tabelas de coerção e precedência
- Use os métodos Number(), String() e Boolean() para ver os resultados das coerções
- Use o === nas comparações para evitar confusões (evita a coerção automática dos tipos)
- Utilize tecnologias como o TypeScript