Dia 3 — Tipos, operadores e comparação
Os tipos de dado e typeof
Os tipos de dado e typeof
Cada valor tem um tipo de dado, e o JavaScript não tem tipos declarados: uma variável pode guardar número e, na
linha seguinte, texto. Mas todo valor tem um tipo, e typeof é o operador que
devolve o nome dele como texto.
console.log(typeof 42); // number console.log(typeof 'Ana'); // string console.log(typeof true); // boolean console.log(typeof {}); // object
São oito tipos, e a lista inteira cabe numa chamada:
| Tipo | Exemplo | typeof |
|---|---|---|
string | 'Ana', ` Ana ` | string |
number | 42, 3.5, 10n | number |
boolean | true, false | boolean |
object | {}, [], null | object |
undefined | variável sem valor | undefined |
symbol | Symbol('id') | symbol |
bigint | 10n | bigint |
function | () => {} | function |
O function não é um tipo primitivo, mas typeof devolve o nome dele mesmo
assim, e é por isso que typeof serve para distinguir função de objeto comum.
A lista da tabela é conferida de uma vez no exemplo: ele junta os dez typeof em
um único objeto e imprime o resultado como `typeof: { string: 'string', number:
'number', ... }`. É a forma de conferir uma tabela sem escrever dez linhas de
console. A linha repara em duas coisas que a tabela deixa implícitas — array
aparece com typeof 'object', e objeto também; funcao aparece com
'function', separado dos dois.
Os três casos que confundem
typeof null devolve 'object'. Isso é um erro histórico do JavaScript, de
1995, que ficou por compatibilidade: a correção quebraria código antigo em todo
o mundo, então ninguém corrigiu. A consequência prática é que `typeof x ===
'object' não significa "tem objeto" — significa "tem objeto ou é null`".
typeof undefined devolve 'undefined', e isso é correto. A diferença entre os
dois importa: undefined é o valor de uma coisa que não existe ainda;
null é um valor que existe e foi posto de propósito para dizer "vazio".
typeof [] devolve 'object', não 'array'. Array é um tipo de objeto
específico, e o jeito de saber se um valor é array é Array.isArray(valor):
console.log(typeof [1, 2]); // object console.log(Array.isArray([1, 2])); // true
A linha do exemplo que trata dos três juntos é
typeof null devolve object: object. Ela imprime o nome do tipo de null e a
resposta é a mesma do objeto. A linha seguinte usa Array.isArray nos dois
candidatos e imprime true false: o array é array, o objeto não é. Perceba a
direção da checagem — Array.isArray é a pergunta "isto é array?", e ela não
depende do typeof.
Os primitivos e os números que não são número
Um primitivo é o valor que vem pronto, sem estrutura interna: string,
number, boolean, undefined, symbol e bigint. Objeto e array não são
primitivos.
Dentro de number existem três valores que não se comportam como número:
NaNé o resultado de uma conta que não faz sentido —'abc' * 2dá
NaN. E NaN não é igual a si mesmo, nem com ===: é o único valor do
JavaScript que não é igual a ele mesmo.
Infinityé o infinito. Sai de1 / 0, e não trava o programa.-0é zero com sinal, eObject.is(-0, 0)distingue os dois quando isso
importa.
console.log(NaN === NaN); // false console.log(Number.isNaN(NaN)); // true console.log(1 / 0); // Infinity
O typeof sozinho não serve para testar se um número é válido, porque NaN é do
tipo number. Para isso o método certo é Number.isNaN.
O exemplo junta os três em duas linhas. 'abc' * 2: NaN | typeof NaN: number
mostra o NaN nascendo de uma conta sem sentido e, ao lado, o tipo que ele
declara — number. E NaN === NaN: false | Number.isNaN(NaN): true mostra os
dois lados da mesma pergunta: o === responde que não, o Number.isNaN
responde que sim. A linha seguinte, 1 / 0: Infinity | typeof Infinity: number,
fecha o grupo: o infinito também é number, e também não derruba o programa.
Número de ponto flutuante não é exato
0.1 + 0.2 não dá 0.3, dá 0.30000000000000004, e a comparação
0.1 + 0.2 === 0.3 é false. Isso não é defeito do React Native nem do seu
código: número de ponto flutuante guarda o valor em binário, e 0,1 e 0,2 não têm
representação exata em binário. Aparece em tela como 0.30000000000000004 e
vira Number(x.toFixed(2)) ou parseFloat(x.toFixed(2)) quando o número vai
para a tela.
O exemplo reproduz os três passos em três linhas: `0.1 + 0.2:
0.30000000000000004, igual a 0.3? false e arredondado para a tela: 0.3`. O
toFixed(2) sozinho resolveria a exibição, mas devolveria texto — e é por isso
que o Number(...) em volta existe: o console mostra 0.3 como número, e a
conta seguinte do app continua funcionando.
O detalhe que fecha a aula é a função testar do exemplo. Ela devolve
objeto para {}, nulo para null e objeto para [] — e o texto da
linha é testar({}): objeto | testar(null): nulo | testar([]): objeto. A
checagem do null vem antes do typeof, porque typeof null é
'object' e a ordem invertida daria a resposta errada. É o mesmo cuidado que a
tabela acima avisa: a pergunta certa sobre objeto nunca é só o typeof.
Exemplo
// Os tipos de dado e o que typeof devolve. O exemplo inclui os tres casos // que confundem: typeof null, typeof array e NaN. const valores = { nome: 'Ana', idade: 30, ativo: true, tarefas: ['estudar'], nada: null, semValor: undefined, }; console.log('typeof:', { string: typeof valores.nome, number: typeof valores.idade, boolean: typeof valores.ativo, objeto: typeof valores, array: typeof valores.tarefas, nulo: typeof valores.nada, indefinido: typeof valores.semValor, funcao: typeof (() => {}), simbolo: typeof Symbol('id'), bigint: typeof 10n, }); console.log('typeof null devolve object:', typeof null); console.log('array e object, o jeito de saber e Array.isArray:', Array.isArray(valores.tarefas), Array.isArray(valores)); // os numeros que nao se comportam como numero console.log("'abc' * 2:", 'abc' * 2, '| typeof NaN:', typeof NaN); console.log('NaN === NaN:', NaN === NaN, '| Number.isNaN(NaN):', Number.isNaN(NaN)); console.log('1 / 0:', 1 / 0, '| typeof Infinity:', typeof Infinity); // ponto flutuante: 0.1 + 0.2 nao e 0.3 console.log('0.1 + 0.2:', 0.1 + 0.2); console.log('igual a 0.3?', 0.1 + 0.2 === 0.3); console.log('arredondado para a tela:', Number((0.1 + 0.2).toFixed(2))); // o typeof nao distingue objeto de null, entao a checagem precisa ser melhor function testarObjetoMal(valor) { return typeof valor === 'object' && valor !== null; } function testar(valor) { if (valor === null) return 'nulo'; return typeof valor === 'object' ? 'objeto' : typeof valor; } console.log('testar({}):', testar({}), '| testar(null):', testar(null), '| testar([]):', testar([]));
Saída real
typeof: {
string: 'string',
number: 'number',
boolean: 'boolean',
objeto: 'object',
array: 'object',
nulo: 'object',
indefinido: 'undefined',
funcao: 'function',
simbolo: 'symbol',
bigint: 'bigint'
}
typeof null devolve object: object
array e object, o jeito de saber e Array.isArray: true false
'abc' * 2: NaN | typeof NaN: number
NaN === NaN: false | Number.isNaN(NaN): true
1 / 0: Infinity | typeof Infinity: number
0.1 + 0.2: 0.30000000000000004
igual a 0.3? false
arredondado para a tela: 0.3
testar({}): objeto | testar(null): nulo | testar([]): objeto
Operadores, comparação e o caso de == contra ===
Operadores, comparação e o caso de == contra ===
A comparação tem duas formas, e a diferença entre elas é a origem de uma
quantidade grande de bug em JavaScript. == compara só o valor, convertendo
um lado para o tipo do outro — a conversão implícita ou type coercion. ===
compara tipo e valor, sem converter nada.
console.log('0' == 0); // true console.log('0' === 0); // false
Usar === sempre é a regra. == não tem caso em que seja a única escolha, e a
lista de valores que ele considera iguais é longa demais para ser memorizada —
ela inclui '0' e 0 — o caso "0" == 0, o mais citado —, '' e 0, [] e
false, null e undefined. Cada um
desses pares passa no == e falha no ===, e nenhum deles é o que o autor do
código queria dizer.
O exemplo percorre a lista inteira em duas linhas, uma por operador. A de ==
imprime '0' == 0: true | 0 == false: true | '' == 0: true e, na linha seguinte,
null == undefined: true | [] == false: true. A de === imprime
'0' === 0: false | 0 === false: false e
null === undefined: false | null == undefined: true. A última linha é a mais
instruativa do exemplo, porque mistura os dois operadores na mesma comparação:
null == undefined é true ao lado de null === undefined que é false, e as
duas afirmações são verdadeiras ao mesmo tempo.
Os operadores aritméticos
| Operador | O que faz | Exemplo | Resultado |
|---|---|---|---|
+ | soma | 2 + 3 | 5 |
- | subtração | 7 - 2 | 5 |
* | multiplicação | 4 * 3 | 12 |
/ | divisão | 7 / 2 | 3.5 |
% | resto (módulo) | 7 % 3 | 1 |
** | potenciação | 2 ** 3 | 8 |
% é o resto da divisão, não o resto de resto: 7 % 3 é 1, e
-7 % 3 é -1, porque o resto herda o sinal do dividendo. O uso mais comum dele
é checar se um número é par (n % 2 === 0) e é o que separa item par de item
ímpar numa lista.
Precedência é a ordem em que o JavaScript resolve os operadores: *, / e
% antes de + e -, e exponenciação antes de todos. 2 + 3 * 4 é 14, não
20. Quando a ordem pretendida é outra, os parênteses escrevem a ordem, e
usar parênteses mesmo quando não é preciso é o estilo que evita a discussão.
A tabela e a precedência são conferidas lado a lado no exemplo. A aritmética
aparece em três linhas, com resto negativo: -1 ao lado de resto: 1 para
mostrar o sinal, e 2 ** 0.5: 1.4142135623730951 para lembrar que a
potenciação aceita fração. A precedência vem na linha seguinte, e é
2 + 3 4: 14 | (2 + 3) 4: 20 — os dois lados da frase do parágrafo, com a
resposta ao lado do resultado que não muda.
O + que concatena e o + que soma
O + é o único operador que decide entre somar e juntar texto conforme os tipos
dos dois lados. Com dois números, soma. Se qualquer lado for string, junta:
console.log('5' + 2); // '52' console.log('5' - 2); // 3 console.log('a' * 2); // NaN
Por isso '10' + 5 - 2 dá 13: o + junta em '105', e o - converte de
volta para número. Esse detalhe é a origem do bug clássico de contador que
incrementa errado, e a saída é converter explicitamente com Number() ou
parseInt() antes de somar.
O exemplo separa os três casos em três linhas com o motivo escrito ao lado de
cada resultado: '5' + 2: 52 (o + concatena), `'5' - 2: 3 (o - forca a
conversao) e 'a' * 2: NaN (letra nao vira numero). O mesmo '5'` é número
na segunda linha e texto na primeira, e a diferença do resultado veio do
operador, não do valor.
Truthy, falsy e o operador duplo
Quando o JavaScript precisa de um valor verdadeiro, ele testa o valor como se
fosse uma condição. Só sete valores são falsy — todos os outros são
truthy:
false, 0, '' (string vazia), null, undefined, NaN e document.all.
Os casos que mais enganam estão no meio dessa lista: a string '0' é truthy
(não é a string vazia), a string ' ' com um espaço é truthy, e o array []
vazio é truthy. A razão é que o JavaScript converte o array em objeto, e objeto
é truthy. O operador ! nega, e !! nega duas vezes, servindo para
transformar o teste em true ou false de verdade.
console.log(!!'', !!'0', !![], !!null); // false true true false
O exemplo testa a lista inteira de uma vez, com o resultado impresso como
objeto: `!! valor: { zero: false, stringVazia: false, textoZero: true, espaco:
true, arrayVazio: true, nulo: false, indefinido: false }`. As quatro linhas que
mais confundem estão todas nesse objeto — textoZero, espaco e arrayVazio
saem true, e stringVazia sai false. O !! é o que produz true/false
em vez do valor original, e é por isso que a linha do exemplo pode ser lida como
uma lista de veredito.
Os operadores unários e os de comparação
Um operador unário age em um lado só: -5 nega o sinal, !x nega o valor,
typeof x devolve o tipo e delete remove a propriedade de um objeto.
typeof funciona com nome não declarado, o que faz dele o teste seguro:
console.log(typeof naoDeclarada); // 'undefined'
As três linhas do exemplo cobrem os quatro operadores unários e mostram por que
o typeof é o teste seguro. A primeira imprime `negativo: -5 | nao: false |
typeof: string — o ! em true dá false, e o typeof do texto 'a'` dá
'string'. A segunda é typeof de quem nao existe: undefined, e nenhuma
declaração de naoDeclarada existe no arquivo: o typeof é a única consulta
que não quebra com nome ausente. A terceira usa o delete e mostra o efeito:
indice de "Ana": A | delete em objeto: e, na linha seguinte,
depois do delete: {} — o objeto existia com nome e ficou sem nenhum campo.
Os de comparação são dois: os de igualdade (==, !=, ===, !==) e os de
ordem (<, >, <=, >=). Para texto, ordem é ordem de letra — `'Ana' <
'Bruno' é true. Para número com texto, use Number()` antes de comparar, ou
a comparação vai comparar string.
Os quatro operadores de igualdade cabem em uma linha, e o exemplo confirma:
'1' != 0: true | '1' !== 0: true. O != nega o ==, e ambos negam o ===,
então os dois são true para o mesmo par. Os quatro de ordem vêm na linha
seguinte, com os quatro casos: `2 > 1: true | 2 >= 2: true | 1 < 2: true |
1 <= 1: true`.
Exemplo
// Operadores, comparacao e a diferenca entre == e ===. const a = '0'; const b = 0; const c = false; const nulo = null; const indefinido = undefined; console.log('--- aritmetica ---'); console.log('soma:', 2 + 3, '| subtracao:', 7 - 2, '| multiplicacao:', 4 * 3); console.log('divisao:', 7 / 2, '| resto:', 7 % 3, '| resto negativo:', -7 % 3); console.log('potencia:', 2 ** 3, '| 2 ** 0.5:', 2 ** 0.5); console.log('--- precedencia ---'); console.log('2 + 3 * 4:', 2 + 3 * 4, '| (2 + 3) * 4:', (2 + 3) * 4); console.log('--- comparacao frouxa: converte antes de comparar ---'); console.log("'0' == 0:", a == b, "| 0 == false:", 0 == c, "| '' == 0:", '' == 0); console.log('null == undefined:', nulo == indefinido, "| [] == false:", [] == c); console.log('--- comparacao estrita: compara tipo e valor ---'); console.log("'0' === 0:", a === b, "| 0 === false:", 0 === c); console.log('null === undefined:', nulo === indefinido, '| null == undefined:', nulo == indefinido); console.log('--- os tres iguais da comparacao ---'); console.log("'1' != 0:", '1' != 0, "| '1' !== 0:", '1' !== 0); console.log('2 > 1:', 2 > 1, '| 2 >= 2:', 2 >= 2, '| 1 < 2:', 1 < 2, '| 1 <= 1:', 1 <= 1); console.log('--- conversao implicita na conta ---'); console.log("'5' + 2:", '5' + 2, "(o + concatena)"); console.log("'5' - 2:", '5' - 2, "(o - forca a conversao)"); console.log("'a' * 2:", 'a' * 2, "(letra nao vira numero)"); console.log('--- truthy e falsy ---'); console.log('!! valor:', { zero: !!0, stringVazia: !!'', textoZero: !!'0', espaco: !!' ', arrayVazio: !![], nulo: !!null, indefinido: !!undefined, }); console.log('--- operadores unarios ---'); console.log('negativo:', -5, '| nao:', !true, '| typeof:', typeof 'a'); console.log('typeof de quem nao existe:', typeof naoDeclarada); console.log('indice de "Ana":', 'Ana'[0], '| delete em objeto:'); const usuario = { nome: 'Ana' }; delete usuario.nome; console.log(' depois do delete:', usuario);
Saída real
--- aritmetica ---
soma: 5 | subtracao: 5 | multiplicacao: 12
divisao: 3.5 | resto: 1 | resto negativo: -1
potencia: 8 | 2 ** 0.5: 1.4142135623730951
--- precedencia ---
2 + 3 * 4: 14 | (2 + 3) * 4: 20
--- comparacao frouxa: converte antes de comparar ---
'0' == 0: true | 0 == false: true | '' == 0: true
null == undefined: true | [] == false: true
--- comparacao estrita: compara tipo e valor ---
'0' === 0: false | 0 === false: false
null === undefined: false | null == undefined: true
--- os tres iguais da comparacao ---
'1' != 0: true | '1' !== 0: true
2 > 1: true | 2 >= 2: true | 1 < 2: true | 1 <= 1: true
--- conversao implicita na conta ---
'5' + 2: 52 (o + concatena)
'5' - 2: 3 (o - forca a conversao)
'a' * 2: NaN (letra nao vira numero)
--- truthy e falsy ---
!! valor: {
zero: false,
stringVazia: false,
textoZero: true,
espaco: true,
arrayVazio: true,
nulo: false,
indefinido: false
}
--- operadores unarios ---
negativo: -5 | nao: false | typeof: string
typeof de quem nao existe: undefined
indice de "Ana": A | delete em objeto:
depois do delete: {}