
Qualquer que seja sua experiência em programação JavaScript, provavelmente você não entende completamente a linguagem. Este guia conciso explora os tipos mais profundamente do que todos os livros existentes: você aprenderá como os tipos funcionam, sobre os problemas de sua conversão e aprenderá como usar novos recursos.
Como outros livros da série
“Você não conhece JS” , ele mostra aspectos não triviais da linguagem dos quais os programadores JavaScript preferem ficar longe (ou presumem que eles não existem). Armado com esse conhecimento, você alcançará o verdadeiro domínio do JavaScript.
Trecho. A igualdade é estrita e não estrita.
A igualdade não estrita é verificada pelo operador ==, e a igualdade estrita pelo operador ===. Ambos os operadores são usados para comparar dois valores para “igualdade”, mas a escolha da forma (estrita / não estrita) leva a diferenças muito importantes no comportamento, especialmente no modo como a decisão é tomada sobre a igualdade.
Há um equívoco comum sobre esses dois operadores: "== verifica a igualdade de valor e === verifica a igualdade de valores e tipos". Parece razoável
mas impreciso. Inúmeros livros e blogs JavaScript respeitáveis dizem exatamente isso, mas infelizmente estão todos errados.
A descrição correta é: "== permite a conversão de tipos ao verificar a igualdade e === proíbe a conversão de tipos."
Desempenho de Verificação de Igualdade
Pare e pense em como a primeira explicação (imprecisa) difere da segunda (exata).
Na primeira explicação, parece óbvio que o operador === faz mais trabalho que == porque também precisa verificar o tipo.
Na segunda explicação, o operador == trabalha mais, porque com tipos diferentes ele precisa passar pela conversão de tipos.
Não caia na armadilha em que muitos caem. Não pense que isso afetará de alguma forma a velocidade do programa e == será significativamente mais lento ===. Embora a conversão demore algum tempo, são necessários alguns microssegundos (sim, milionésimos de segundo).
Se você estiver comparando dois valores do mesmo tipo, == e === use o mesmo algoritmo; portanto, se você não considerar pequenas diferenças na implementação do mecanismo, elas deverão executar um
e o mesmo trabalho.
Se você estiver comparando dois valores de tipos diferentes, o desempenho não é um fator importante. Você precisa se perguntar outra coisa: se estou comparando dois valores, quero que a conversão de tipos aconteça ou não?
Se você precisar de uma conversão, use igualdade não estrita == e, se a conversão for indesejável, use igualdade estrita ===.
Os dois operadores == e === verificam os tipos de seus operandos. A diferença é como eles respondem à incompatibilidade de tipos.
Verificação abstrata de igualdade
O comportamento do operador == é definido na seção 11.9.3 da especificação do ES5 (“Algoritmo do Verificador de Igualdade Abstrata”). Aqui está um algoritmo detalhado, mas simples, com uma lista explícita de todas as combinações possíveis de tipos e métodos de conversão de tipos (se necessário) que devem ser aplicados em cada combinação.
Quando alguém condena a conversão de tipo (implícita) por ser muito complexa e conter muitos defeitos para uso prático útil, condena as regras da "verificação abstrata da igualdade". Costuma-se dizer que esse mecanismo é muito complicado e antinatural para estudo e uso prático, e que cria erros nos programas JS, em vez de simplificar a leitura do código.
Acredito que essa é uma suposição errônea - porque vocês, leitores, são desenvolvedores competentes que escrevem algoritmos, ou seja, código (e também o leem e entendem), o dia todo. Por esse motivo, tentarei explicar o "teste de igualdade abstrata" em palavras simples. No entanto, também recomendo a leitura da seção 11.9.3 da especificação ES5. Eu acho que vai surpreender você como tudo está lógico.
De fato, a primeira seção (11.9.3.1) afirma que, se dois valores comparados são do mesmo tipo, eles são comparados de maneira simples e natural. Por exemplo, 42 é apenas 42 e a string "abc" é apenas "abc".
Algumas pequenas exceções a serem lembradas:
- O valor de NaN nunca é igual a si mesmo (consulte o capítulo 2).
- +0 e -0 são iguais entre si (consulte o capítulo 2).
A última seção na seção 11.9.3.1 é dedicada a um teste rigoroso de == igualdade com objetos (incluindo funções e matrizes). Dois desses valores são iguais
apenas se ambos se referirem exatamente ao
mesmo valor . Nenhuma conversão de tipo é realizada.
Uma verificação rigorosa de igualdade é definida de forma idêntica à 11.9.3.1, incluindo a provisão para dois valores de objeto. Esse fato é muito pouco conhecido, mas == e === se comportam completamente idênticos ao comparar dois objetos!
O restante do algoritmo em 11.9.3 indica que a igualdade livre == pode ser usada para comparar dois tipos diferentes de valores, um ou ambos exigindo
conversão implícita. Como resultado da conversão, as designações são convertidas em um tipo, após o qual podem ser comparadas diretamente para igualdade por simples identidade
valores.
A operação de uma fraca verificação da desigualdade! = É determinada exatamente como seria de esperar; de fato, a operação == é totalmente implementada, seguida pelo cálculo
negação do resultado. O mesmo se aplica à operação de verificar rigorosamente a desigualdade! ==.
Comparação: strings e números
Para demonstrar a conversão de ==, primeiro crie exemplos de seqüências de caracteres e números, o que foi feito anteriormente neste capítulo:
var a = 42; var b = "42"; a === b;
Como esperado, a verificação a === b falha porque a conversão não é permitida e os valores 42 e "42" são diferentes.
No entanto, na segunda comparação a == b, a igualdade não estrita é usada; isso significa que, se os tipos forem diferentes, o algoritmo de comparação executará uma conversão implícita de um
ou ambos.
Mas que tipo de conversão está sendo realizada aqui? O valor a, ou seja, 42, se tornará uma string ou o valor b "42" se tornará um número? A especificação ES5 nas seções 11.9.3.4-5 diz:
- Se Type (x) for do tipo Number e Type (y) for do tipo String, retorne o resultado da comparação x == ToNumber (y).
- Se o Tipo (x) for do tipo String e o Tipo (y) for do tipo Número, retorne o resultado da comparação ToNumber (x) == y.
Na especificação, nomes formais dos tipos Número e String são usados, enquanto no livro para tipos primitivos o número e a string da notação são geralmente usados. Não confunda a caixa do símbolo Number na especificação com a função Number () incorporada. Para nossos propósitos, o caso de caracteres no nome do tipo não desempenha um papel - eles significam a mesma coisa.
A especificação diz que o valor "42" é convertido em um número para comparação. Sobre como a conversão é executada, ela já foi descrita anteriormente e, especificamente, ao descrever a operação abstrata ToNumber. Nesse caso, é bastante óbvio
que os dois valores resultantes de 42 são iguais.
Comparação: qualquer coisa com booleanos
Uma das armadilhas mais perigosas na conversão implícita do tipo == é encontrada ao tentar comparar diretamente valores com true ou false.
Um exemplo:
var a = "42"; var b = true; a == b;
Espere, o que está acontecendo aqui? Sabemos que "42" é o verdadeiro significado (veja anteriormente neste capítulo). Como é que comparando-o com verdadeiro com a afirmação de igualdade estrita ==
não dá verdade?
A razão é simples e enganosamente esperta ao mesmo tempo. É fácil entender mal, muitos desenvolvedores de JS não se esforçam para entendê-lo completamente.
Mais uma vez citamos a especificação, seções 11.9.3.6–7:
- Se o Tipo (x) for do tipo Booleano, retorne o resultado da comparação ToNumber (x) == y.
- Se o Tipo (y) for do tipo Booleano, retorne o resultado da comparação x == ToNumber (y).
Vamos ver o que está aqui. Primeiro passo:
var x = true; var y = "42"; x == y;
O tipo (x) realmente pertence ao tipo booleano; portanto, a operação ToNumber (x) é executada, que é verdadeira como 1. Agora, a condição 1 == "42" é calculada. Os tipos ainda são diferentes, portanto (quase recursivamente) o algoritmo se repete; como no caso anterior, "42" é convertido em 42 e a condição 1 == 42 é obviamente falsa.
Se você trocar operandos, o resultado permanecerá o mesmo:
var x = "42"; var y = false; x == y;
Desta vez, o Tipo (y) é do tipo Booleano, então ToNumber (y) fornece 0. A condição "42" == 0 recursivamente se transforma em 42 == 0, o que, é claro, é falso.
Em outras palavras, o valor "42" não é == verdadeiro nem == falso. À primeira vista, essa afirmação parece completamente impensável. Como o significado pode não ser verdadeiro nem falso?
Mas este é o problema! Você está fazendo a pergunta errada. Embora na verdade não seja sua culpa, é o cérebro que está enganando você.
O valor "42" é realmente verdadeiro, mas a construção "42" == true não realiza um teste booleano / de transformação, seja o que for que seu cérebro diga. "42" não converte em booleano (true); em vez disso, true é convertido em 1 e, em seguida, "42" é convertido em 42.
Quer você goste ou não, o ToBoolean não é usado aqui, portanto a verdade ou falsidade de "42" não é importante para a operação ==! É importante entender como o algoritmo de comparação == se comporta em todas as diferentes combinações de tipos. Se o valor booleano estiver em um lado, ele sempre será convertido em um número primeiro.
Se isso lhe parecer estranho, você não está sozinho. Pessoalmente, recomendo que nunca, nunca, em nenhuma circunstância, use == true ou == false. Nunca.
Mas lembre-se de que estou falando apenas de == aqui. As construções === true e === false não permitem a conversão de tipos; portanto, elas são protegidas da conversão oculta de ToNumber.
Um exemplo:
var a = "42";
Se você evitar == verdadeiro ou == falso (igualdade frouxa com booleano) em seu código, nunca precisará se preocupar com essa armadilha de verdade / falsidade.
Comparação: nula com indefinida
Outro exemplo de conversão implícita ocorre quando você usa a igualdade lax == entre valores nulos e indefinidos. Mais uma vez, citarei a especificação ES5,
seções 11.9.3.2-3:
- Se x contiver nulo e y contiver indefinido, retorne true.
- Se x contiver indefinido e y contiver nulo, retorne true.
Nulo e indefinido quando comparado com o operador não estrito == são iguais um ao outro (isto é, são convertidos um ao outro) e nenhum outro valor em todo o idioma.
Para nós, isso significa que nulo e indefinido podem ser considerados indistinguíveis para fins de comparação se você usar o operador de teste de igualdade não estrito ==, que permite a conversão implícita mútua:
var a = null; var b; a == b;
A conversão entre nulo e indefinido é segura e previsível, e nenhum outro valor pode fornecer falsos positivos para essa verificação. Eu recomendo usar esta conversão para que nulo e indefinido não sejam diferentes no programa e sejam interpretados como um valor único.
Um exemplo:
var a = doSomething(); if (a == null) {
A verificação a == null só passa se doSomething () retornar nulo ou indefinido e falhar para qualquer outro valor (incluindo 0, false e "").
A forma explícita dessa verificação, que proíbe conversões desse tipo, parece (na minha opinião) muito mais feia e pode funcionar um pouco menos eficientemente!
var a = doSomething(); if (a === undefined || a === null) {
Acredito que o formato a == null é outro exemplo de uma situação em que uma conversão implícita facilita a leitura do código, mas o faz de maneira confiável e segura.
Comparação: objetos e não-objetos
Se um objeto / função / matriz é comparado com uma primitiva escalar simples (string, número ou booleano), a especificação ES5 diz o seguinte (seção 11.9.3.8–9):
- Se o Tipo (x) for do tipo String ou Number e o Tipo (y) for do tipo Objeto, retorne o resultado da comparação x == ToPrimitive (y).
- Se o Tipo (x) for do tipo Objeto e o Tipo (y) for do tipo String ou Number, retorne o resultado da comparação ToPrimitive (x) == y.
Você deve ter notado que nessas seções da especificação apenas String e Number são mencionados, mas não booleanos. O fato é que, como mencionado acima, as seções 11.9.3.6–7 garantem que qualquer operando booleano seja primeiro representado como Número.
Um exemplo:
var a = 42; var b = [ 42 ]; a == b;
Para o valor [42], a operação abstrata ToPrimitive é chamada (consulte "Operações abstratas"), que fornece o resultado "42". A partir deste momento, permanece a condição simples "42" == 42, que, como já descobrimos, se transforma em 42 == 42, de modo que aeb são iguais à conversão de tipo.
Como seria de esperar, todos os recursos da operação ToPrimitive abstrata discutida anteriormente neste capítulo ((toString (), valueOf ()) também são aplicáveis neste caso. Isso pode ser muito útil se você tiver uma estrutura de dados complexa e desejar defina um método especializado valueOf () para ele, que precisará fornecer um valor simples para fins de verificação da igualdade.
O Capítulo 3 examinou a "descompactação" de um invólucro de objeto em torno de um valor primitivo (como na nova String ("abc"), por exemplo), resultando no retorno da primitiva subjacente
valor ("abc"). Esse comportamento está relacionado à transformação ToPrimitive no algoritmo ==:
var a = "abc"; var b = Object( a );
a == b é verdadeiro porque b é convertido (ou "descompactado") pela operação ToPrimitive no valor primitivo escalar simples básico "abc", que corresponde ao valor de a.
Existem alguns valores para os quais isso não ocorre devido a outras regras de substituição no algoritmo ==. Um exemplo:
var a = null; var b = Object( a );
Os valores nulos e indefinidos não podem ser compactados (eles não têm um invólucro de objeto equivalente); portanto, Object (null) não é fundamentalmente diferente de Object (): as duas chamadas criam o usual
qualquer objeto.
O NaN pode ser empacotado no invólucro de objeto Number equivalente, mas quando == causa a descompactação, a comparação NaN == NaN falha porque o valor NaN nunca é igual a si mesmo (consulte o capítulo 2).
»Mais informações sobre o livro podem ser encontradas no
site do editor»
Conteúdo»
TrechoCupom de 25% para vendedores ambulantes -
JavaScriptApós o pagamento da versão impressa do livro, um livro eletrônico é enviado por e-mail.