analisando os exemplos de JavaScript na palestra “Wat” de Gary Bernhardt

este post é uma homenagem à fantástica palestra “Wat” de Gary Bernhardt, na qual ele aponta as peculiaridades de algumas construções de linguagem em Ruby e JavaScript. Se você ainda não assistiu a palestra, recomendo fortemente que você reserve um tempo e faça exatamente isso! É apenas cerca de 4 minutos de duração e altamente divertido, eu prometo.

em sua palestra, Gary mostra esses quatro fragmentos de código JavaScript:

peculiaridades em JavaScript

vemos muitos colchetes, chaves e sinais de mais. Aqui está o que esses fragmentos avaliar a:

  • + == ""
  • + {} == ""
  • {} + == 0
  • {} + {} == NaN

Quando eu vi esses exemplos, pela primeira vez, eu pensei: “Uau, isso parece confuso!”Os resultados podem parecer inconsistentes ou mesmo arbitrários, mas tenham paciência comigo aqui. Todos esses exemplos são realmente muito consistentes e não tão ruins quanto parecem!

# fragmento #1: +

vamos começar com o primeiro fragmento:

 + // ""

como podemos ver, aplicar o operador + a duas matrizes vazias resulta em uma string vazia. Isso ocorre porque a representação de string de uma matriz é a representação de string de todos os seus elementos, concatenada junto com vírgulas:

.toString()// "1,2,3".toString()// "1,2".toString()// "1".toString()// ""

uma matriz vazia não contém nenhum elemento, portanto, sua representação de string é uma string vazia. Portanto, a concatenação de duas strings vazias é apenas mais uma string vazia.

# fragmento #2: + {}

até agora, tão bom. Vamos agora examinar o segundo fragmento:

 + {}// ""

observe que, como não estamos lidando com dois números, o operador + mais uma vez executa concatenação de string em vez de adicionar dois valores numéricos.

na seção anterior, já vimos que a representação de string de uma matriz vazia é uma string vazia. A representação de string do literal de objeto vazio aqui é o valor padrão "". Prepending uma string vazia não altera o valor, então "" é o resultado final.

em JavaScript, os objetos podem implementar um método especial chamado toString() que retorna uma representação de string personalizada do objeto no qual o método é chamado. Nosso literal de objeto vazio não implementa esse método, então estamos voltando à implementação padrão do protótipo Object.

# fragmento #3: {} +

eu diria que até agora, os resultados não foram muito inesperados. Eles simplesmente seguiram as regras de coerção de tipo e representações de string padrão em JavaScript.

no entanto, {} + é onde os desenvolvedores começam a ficar confusos:

{} + // 0

por que vemos 0(o número zero) se digitamos a linha acima em um REPL JavaScript como o console do navegador? O resultado não deveria ser uma string, assim como + {} era?

Antes de resolver o enigma, considere as três diferentes formas de o + operador pode ser usado:

// 1) Addition of two numeric values2 + 2 == 4// 2) String concatenation of two values"2" + "2" == "22"// 3) Conversion of a value to a number+2 == 2+"2" == 2

Nos dois primeiros casos, o + operador é um operador binário pois possui dois operandos (à esquerda e à direita). No terceiro caso, o operador + é um operador unário porque possui apenas um único operando (à direita).

considere também os dois significados possíveis de {} em JavaScript. Normalmente, escrevemos {} para significar um literal de objeto vazio, mas se estivermos na posição de instrução, a gramática JavaScript especifica {} para significar um bloco vazio. O seguinte pedaço de código define dois blocos vazios, nenhum dos quais é um literal de objeto:

{}// Empty block{ // Empty block}

Vamos dar uma olhada em nossa fragmento novamente:

{} + 

Deixe-me mudar o espaço em branco um pouco para torná-lo mais claro como o motor de JavaScript vê o código:

{ // Empty block}+;

agora podemos ver claramente o que está acontecendo aqui. Temos uma instrução block seguida por outra instrução que contém uma expressão unary + operando em uma matriz vazia. O ponto e vírgula à direita é inserido automaticamente de acordo com as regras do ASI (inserção automática de ponto e vírgula).

você pode verificar facilmente no console do navegador que + avalia como 0. A matriz vazia tem uma string vazia como sua representação de string, que por sua vez é convertida para o número zero pelo operador +. Finalmente, o valor da última instrução (+, neste caso) é relatado pelo console do navegador.

Alternativamente, você pode alimentar ambos os trechos de código para um analisador JavaScript, como Esprima e comparar as árvores de sintaxe abstratas resultantes. Aqui está a AST + {}:

{ "type": "Program", "body": }, "right": { "type": "ObjectExpression", "properties": } } } ], "sourceType": "script"}

E aqui está o AST {} + :

{ "type": "Program", "body": }, { "type": "ExpressionStatement", "expression": { "type": "UnaryExpression", "operator": "+", "argument": { "type": "ArrayExpression", "elements": }, "prefix": true } } ], "sourceType": "script"}

A confusão deriva de uma nuance do JavaScript gramática que usa chaves, tanto para literais de objeto e blocos. Na posição de instrução, uma cinta de abertura inicia um bloco, enquanto na posição de expressão uma cinta de abertura inicia um literal de objeto.

#Fragment #4: {} + {}

Finalmente, vamos, rapidamente, dê uma olhada no nosso último fragmento {} + {}:

{} + {}// NaN

Bem, a adição de dois literais de objeto é, literalmente, “não é um número” — mas nós estamos adicionando dois literais de objeto aqui? Não deixe as chaves enganá-lo novamente! Isso é o que está acontecendo:

{ // Empty block}+{};

é praticamente o mesmo negócio que no exemplo anterior. No entanto, agora estamos aplicando o operador unary plus a um literal de objeto vazio. Isso é basicamente o mesmo que fazer Number({}), o que resulta em NaN porque nosso literal de objeto não pode ser convertido em um número.

se você deseja que o mecanismo JavaScript analise o código como dois literais de objeto vazios, envolva o primeiro (ou todo o código) entre parênteses. Agora você deve ver o resultado esperado:

({}) + {}// ""({} + {})// ""

o parêntese de abertura faz com que o analisador tente reconhecer uma expressão, e é por isso que ele não trata o {} como um bloco (o que seria uma declaração).

#resumo

agora você deve ver por que os quatro fragmentos de código avaliam a maneira como eles fazem. Não é arbitrário ou aleatório; as regras de coerção de tipo são aplicadas exatamente como estabelecido na especificação e na gramática da linguagem.

lembre-se de que, se uma chave de abertura for o primeiro caractere a aparecer em uma instrução, ela será interpretada como o início de um bloco em vez de um literal de objeto.

Deixe uma resposta

O seu endereço de email não será publicado.