Dia 3 — Tipos, operadores e comparacao
Os tipos de dado e typeof
Os tipos de dado e typeof
São poucos tipos, e a linguagem não deixa declarar o tipo da variável. A tipagem é dinâmica: quem tem tipo é o valor, não o nome que o aponta.
O que existe para perguntar o tipo é o operador typeof, que devolve o nome do tipo em texto, em inglês e em minúsculas.
typeof 42; // 'number' typeof 'texto'; // 'string' typeof true; // 'boolean' typeof x; // 'undefined', se x não existe
typeof é o único operador que não quebra em nome inexistente, e por isso é a forma de o código descobrir o que já foi criado.
Os tipos e o que cada typeof devolve
| Valor | typeof devolve | Nota |
|---|---|---|
42, 4.5 | 'number' | inteiro e decimal são o mesmo tipo |
'texto' | 'string' | |
true | 'boolean' | só true e false |
undefined | 'undefined' | nome declarado sem valor, ou não declarado |
null | 'object' | o caso que engana |
[1, 2, 3] | 'object' | array não tem tipo próprio no typeof |
{ nome: 'Ana' } | 'object' | |
10n | 'bigint' | inteiro além do limite seguro |
Symbol('id') | 'symbol' | identidade única |
() => {} | 'function' | função tem tipo próprio |
O tipo de dado é a classificação que o typeof devolve, escrita em texto: 'number', 'string', 'object'. Ele responde pelo valor, não pelo nome — typeof idade e typeof 30 dão a mesma resposta, e é por isso que a mesma variável pode ter tipos diferentes em linhas diferentes.
null devolve 'object' desde 1995, quando typeof foi escrito. O tipo se chamava null e a cola de bits que representa o valor vazio era a mesma usada por objeto. Corrigir quebraria código publicado na web, então a linguagem manteve. Nunca decida o tipo de null com typeof.
Array e objeto, a mesma resposta
typeof array não tem resposta própria: typeof [1, 2, 3] devolve 'object', o mesmo que um objeto comum.
Como o typeof não distingue array de objeto, a forma de separar é Array.isArray.
| Função | Assinatura | Exemplo |
|---|---|---|
Array.isArray | Array.isArray(valor: unknown): boolean | Array.isArray(lista) devolve true |
Object.prototype.toString | Object.prototype.toString.call(valor): string | devolve '[object Array]' |
Os números que não se comportam
NaN e Infinity não se escrevem: nascem de contas. NaN significa "not a number", e é o resultado de uma conta inválida.
0 / 0; // NaN 'abc' * 2; // NaN 1 / 0; // Infinity -1 / 0; // -Infinity NaN === NaN; // false: nem consigo mesmo ele é igual
| Função | Assinatura | Para que serve |
|---|---|---|
Number.isNaN | Number.isNaN(valor: unknown): boolean | dizer se o valor é mesmo NaN |
Number.isFinite | Number.isFinite(valor: unknown): boolean | dizer se é número finito |
Number.isInteger | Number.isInteger(valor: unknown): boolean | dizer se é inteiro |
O NaN é o único valor que não é igual a si mesmo, e é por isso que NaN === NaN dá false. Comparar NaN com == também dá false; só Number.isNaN responde certo.
Ponto flutuante, e pedir o valor em texto
0.1 + 0.2 não é exatamente 0.3. A representação binária de 0.1 é infinita, e o truncamento deixa um resto. Comparar dinheiro assim dá bug.
0.1 + 0.2 === 0.3; // false Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON; // true
Acima de Number.MAX_SAFE_INTEGER (9.007.199.254.740.991), dois inteiros diferentes podem virar o mesmo valor, porque não há bits para diferenciar. Para dinheiro, guarde centavos em inteiro: 1999 em vez de 19.99.
Do número para o texto existem duas formas, e a diferença é o que cada uma aceita.
| Forma | Assinatura | Nota |
|---|---|---|
String | String(valor?: any): string | funciona com qualquer valor, até null |
toString | toString(): string | precisa de um valor que tenha o método |
toString | toString(radix: number): string | com argumento, muda a base |
(255).toString(16) devolve 'ff', (42).toString(2) devolve '101010'. É a forma de ler número em outra base, e não existe equivalente em PHP.
String(42)funciona emnulleundefined;(null).toString()quebra comTypeError. Quando o valor pode ser qualquer um, o caminho éString().
Exemplo
'use strict'; // ============================================================ // AULA 1 - Os tipos de dado e typeof // // São poucos tipos, e a linguagem não deixa declarar o tipo da // variável. O que existe é `typeof`: um operador que devolve o // nome do tipo em texto. Este exemplo lista os tipos, mostra o // caso que engana (`typeof null`) e os números que não se // comportam como número. // ============================================================ // Os tipos primitivos, um por linha. O nome que `typeof` devolve // já vem pronto, em inglês e em minúsculas. console.log('--- typeof dos valores básicos ---'); console.log('42 ->', typeof 42); console.log("'texto' ->", typeof 'texto'); console.log('true ->', typeof true); console.log('10n ->', typeof 10n, '(número inteiro enorme)'); console.log("Symbol('id') ->", typeof Symbol('id')); console.log('função ->', typeof (function () {})); // `semValor` nunca foi declarada e `vazio` foi declarada sem valor. // Nos dois casos o valor é undefined, e o `typeof` não quebra: // perguntar o tipo de um nome inexistente é seguro. const vazio = null; console.log('sem valor ->', typeof semValor, '(nome nunca declarado)'); console.log('nulo ->', typeof vazio, '(const vazio = null)'); // Array e objeto são os dois `object`. O `typeof` não distingue: // devolve a mesma palavra para os dois. const lista = [1, 2, 3]; const ficha = { nome: 'Ana' }; console.log(''); console.log('--- dois tipos, a mesma resposta ---'); console.log('typeof lista:', typeof lista); console.log('typeof ficha:', typeof ficha); console.log('Array.isArray(lista):', Array.isArray(lista)); console.log('Array.isArray(ficha):', Array.isArray(ficha)); console.log('Object.prototype.toString.call(lista):', Object.prototype.toString.call(lista)); // O caso que faz perder nota: `typeof null` devolve 'object'. // `null` não é um objeto, é o valor que significa "vazio de // propósito". O tipo se chama `null` e devolve `object`; é assim // que o `typeof` foi escrito, e a linguagem mantém o // comportamento para não quebrar código antigo. console.log(''); console.log('--- o caso que engana ---'); console.log('typeof null:', typeof null); console.log('typeof undefined:', typeof undefined); console.log('null e undefined são o mesmo valor?', null === undefined); console.log('são do mesmo tipo?', typeof null === typeof undefined); // NaN e Infinity não se escrevem: nascem de contas. NaN significa // "not a number", e é o resultado de uma conta inválida. console.log(''); console.log('--- números que não se comportam ---'); console.log('0 / 0 =', 0 / 0, '(NaN)'); console.log("'abc' * 2 =", 'abc' * 2, '(NaN)'); console.log('1 / 0 =', 1 / 0, '(Infinity)'); console.log('-1 / 0 =', -1 / 0, '(-Infinity)'); console.log('NaN === NaN:', NaN === NaN, '(nem consigo mesmo ele é igual)'); console.log('Number.isNaN(NaN):', Number.isNaN(NaN)); console.log('Number.isFinite(1 / 0):', Number.isFinite(1 / 0)); console.log('Number.isInteger(4.0):', Number.isInteger(4.0)); console.log('Number.isInteger(4.5):', Number.isInteger(4.5)); // Ponto flutuante tem limite de precisão: 0.1 + 0.2 não é // exatamente 0.3. Acima de Number.MAX_SAFE_INTEGER, dois números // inteiros diferentes podem virar o mesmo valor. console.log(''); console.log('--- o limite do ponto flutuante ---'); console.log('0.1 + 0.2 =', 0.1 + 0.2); console.log('0.1 + 0.2 === 0.3:', 0.1 + 0.2 === 0.3); console.log('comparando com tolerância:', Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON); console.log('Number.MAX_SAFE_INTEGER:', Number.MAX_SAFE_INTEGER); // String() é a forma de transformar qualquer valor em texto. Vale // para todos, inclusive null e undefined. console.log(''); console.log('--- do número para o texto ---'); console.log('String(42):', String(42), typeof String(42)); console.log('String(4.5):', String(4.5)); console.log('String(true):', String(true)); console.log('(42).toString():', (42).toString()); console.log('(42).toString(2):', (42).toString(2), '(42 em binário)'); console.log('(255).toString(16):', (255).toString(16), '(255 em hexadecimal)'); console.log('(255).toString(2).length:', (255).toString(2).length, '(bits do número)');
Saída real
--- typeof dos valores básicos ---
42 -> number
'texto' -> string
true -> boolean
10n -> bigint (número inteiro enorme)
Symbol('id') -> symbol
função -> function
sem valor -> undefined (nome nunca declarado)
nulo -> object (const vazio = null)
--- dois tipos, a mesma resposta ---
typeof lista: object
typeof ficha: object
Array.isArray(lista): true
Array.isArray(ficha): false
Object.prototype.toString.call(lista): [object Array]
--- o caso que engana ---
typeof null: object
typeof undefined: undefined
null e undefined são o mesmo valor? false
são do mesmo tipo? false
--- números que não se comportam ---
0 / 0 = NaN (NaN)
'abc' * 2 = NaN (NaN)
1 / 0 = Infinity (Infinity)
-1 / 0 = -Infinity (-Infinity)
NaN === NaN: false (nem consigo mesmo ele é igual)
Number.isNaN(NaN): true
Number.isFinite(1 / 0): false
Number.isInteger(4.0): true
Number.isInteger(4.5): false
--- o limite do ponto flutuante ---
0.1 + 0.2 = 0.30000000000000004
0.1 + 0.2 === 0.3: false
comparando com tolerância: true
Number.MAX_SAFE_INTEGER: 9007199254740991
--- do número para o texto ---
String(42): 42 string
String(4.5): 4.5
String(true): true
(42).toString(): 42
(42).toString(2): 101010 (42 em binário)
(255).toString(16): ff (255 em hexadecimal)
(255).toString(2).length: 8 (bits do número)
Operadores, comparacao e o caso de == contra ===
Operadores, comparação e o caso de == contra ===
Operador é o símbolo que opera sobre valores. A aritmética é conhecida; a parte que perde nota é a comparação, porque existem dois operadores de igualdade e eles não fazem a mesma coisa.
A regra da aula cabe numa frase: use === e nunca ==.
Aritmética e precedência
| Operador | Assinatura | Exemplo | Nota |
|---|---|---|---|
+ | a + b | 10 + 3 → 13 | soma ou junta texto |
- | a - b | 10 - 3 → 7 | |
* | a * b | 10 * 3 → 30 | |
/ | a / b | 10 / 3 → 3.3333… | divisão real, sempre com casas |
% | a % b | 10 % 3 → 1 | resto da divisão |
** | a ** b | 2 ** 10 → 1024 | potência |
O % devolve o resto, e é assim que se descobre se o número é par (n % 2 === 0) ou se a divisão é exata. O resto leva o sinal do dividendo: -7 % 3 dá -1, não 2.
O % é o operador de módulo da divisão — em inglês módulo, que é o nome que a documentação usa. Em português o resultado é o resto, e é por isso que as duas palavras aparecem lado a lado.
Divisão nunca é inteira. 10 / 2 dá 5, e 10 / 4 dá 2.5. Para cortar a parte decimal existe Math.floor, assunto da aula 3.
A precedência vem antes: da mais forte para a mais fraca, **, depois * e /, depois + e -. Depois vêm ===, !== e os operadores lógicos.
2 + 3 * 4; // 14: a multiplicação roda antes (2 + 3) * 4; // 20: o parêntese manda 2 ** 3 ** 2; // 512: a potência é da direita para a esquerda 20 - 5 - 3; // 12: a subtração é da esquerda para a direita
A potência é a única exceção: ela se junta pela direita, então 2 3 2 vale 2 (3 2). Quando a intenção não for óbvia, os parênteses resolvem — e são eles que mudam o resultado de 2 + 3 * 4 para 20.
O + que muda de sentido
O + é o único operador que aceita dois tipos: com dois números soma, com algum texto junta. E a soma vai da esquerda para a direita, então o tipo muda no meio da expressão.
1 + 1 + '2'; // '22': 1+1 fez 2, e 2+'2' juntou '1' + 1 + 1; // '11': o texto puxou o resto 'Soma: ' + 2 + 2; // 'Soma: 22': o texto engoliu a soma 'Soma: ' + (2 + 2); // 'Soma: 4'
'Resultado: ' + 10 / 2 dá 'Resultado: 5', e não 'Resultado: 102': a divisão tem precedência maior e roda antes. Quando a intenção é concatenar números, use a crase, que não tem esse problema.
== contra ===
=== compara valor e tipo. == compara só o valor, depois de converter um dos lados — a conversão implícita, o *type coercion*. É ela que produz as linhas que o aluno vê e não acredita.
| Comparação | == | === | O que aconteceu no == |
|---|---|---|---|
'0' == 0 | true | false | texto convertido para número |
0 == false | true | false | false virou 0 |
'' == 0 | true | false | texto vazio virou 0 |
[] == false | true | false | array vazio virou texto vazio |
null == undefined | true | false | os dois viraram 0 |
A regra da conversão: quando os tipos são diferentes, o == converte o número para o tipo do outro, não o contrário. Por isso '0' == 0 é true e '1' == '0' é false.
Como comparar do jeito certo é deixar os dois lados no mesmo tipo antes:
Number('0') === 0; // true String(0) === '0'; // true ' 10 '.trim() === '10'; // true
Os operadores != e !== são a negação de == e ===, com a mesma diferença de comportamento entre eles.
O <= e o >= fecham a lista: são "menor ou igual" e "maior ou igual", e aceitam os dois tipos sem converter nada. nota >= 7 é true para 7, 8 e 9, e a forma curta de escrever "nota não passou" é nota < 7. O que a linguagem não tem é um "entre": min <= x <= max não funciona, porque o JavaScript avalia comparação por comparação, e o resultado do primeiro <= seria comparado com max. A forma correta é usar dois if aninhados, ou comparar as duas condições com &&.
Comparação de texto, ordem e operadores unários
Texto é comparado por ordem de caractere, não por valor de número, mesmo quando parece número: '10' < '9' é true, porque o 1 vem antes do 9. Com maiúscula e minúscula, 'a' < 'B' é false: as maiúsculas vêm antes.
Operador unário é o que pega un valor: ! nega o booleano, - nega o número, typeof pergunta o tipo, void devolve undefined, e delete remove propriedade de objeto devolvendo true ou false.
const ficha = { nome: 'Ana', idade: 30, cidade: 'Recife' }; delete ficha.idade; // true: removeu delete ficha.inexistente; // false: não havia o que remover
!! é a dupla negação que entrega o booleano: !!'texto' é true e !!0 é false. Converte qualquer valor em true ou false, e é o atalho para a lista de valores falsos da aula 5.
O que essa negação negaria é a lista falsy: os poucos valores que valem false sozinhos — 0, '', null, undefined e NaN. Qualquer outro valor é truthy, inclusive o texto '0' e o array []. A lista completa, com o porquê de cada caso, é a aula 5.
'0'é truthy: texto não vazio, mesmo com o caractere0dentro. O que é falso é o texto vazio.'0' == 0dátruepor conversão, não porque os dois valham a mesma coisa — e essa diferença é o que a aula 5 abre.
Exemplo
'use strict'; // ============================================================ // AULA 2 - Operadores, comparação e o caso de == contra === // // Operador é o símbolo que opera sobre valores. A aula inteira // cabe numa regra: use === e nunca ==. O exemplo mostra a // aritmética, a precedência e a tabela de comparação frouxa que // faz o aluno perder nota. // ============================================================ // Aritmética: os quatro operadores, mais o resto e a potência. console.log('--- aritmética ---'); console.log('10 + 3 =', 10 + 3); console.log('10 - 3 =', 10 - 3); console.log('10 * 3 =', 10 * 3); console.log('10 / 3 =', 10 / 3, '(divisão real, sempre com casas decimais)'); console.log('10 % 3 =', 10 % 3, '(resto da divisão)'); console.log('-7 % 3 =', -7 % 3, '(o resto leva o sinal do dividendo)'); console.log('2 ** 10 =', 2 ** 10, '(potência)'); console.log('17 / 5 =', 17 / 5, '| 17 % 5 =', 17 % 5, '| o mesmo corte'); // Precedência: ** vem antes de * e /, que vem antes de + e -. // Os operadores vão da esquerda para a direita, com uma exceção: a // potência é da direita para a esquerda. console.log(''); console.log('--- precedência ---'); console.log('2 + 3 * 4 =', 2 + 3 * 4); console.log('(2 + 3) * 4 =', (2 + 3) * 4); console.log('2 ** 3 ** 2 =', 2 ** 3 ** 2, '(da direita para a esquerda)'); console.log('20 - 5 - 3 =', 20 - 5 - 3, '(subtração é da esquerda para a direita)'); // O `+` é o único operador que aceita dois tipos: com dois números // soma, com algum texto junta. É por isso que 1 + 1 + '2' dá '22'. console.log(''); console.log('--- o + que muda de sentido ---'); console.log('1 + 1 + "2" =', 1 + 1 + '2'); console.log('"1" + 1 + 1 =', '1' + 1 + 1); console.log('"Resultado: " + 10 / 2 =', 'Resultado: ' + 10 / 2, '(a divisão roda antes do +)'); console.log('String(10 / 2) + "" =', String(10 / 2) + ''); // === compara valor E tipo. == compara só o valor, depois de // converter um dos lados: é essa conversão implícita, o type // coercion, que produz as linhas que o aluno vê e não acredita. console.log(''); console.log('--- == contra === ---'); console.log('"0" == 0 ->', '0' == 0, '(texto convertido para número)'); console.log('"0" === 0 ->', '0' === 0, '(tipos diferentes, não são o mesmo valor)'); console.log('0 == false ->', 0 == false, '(false convertido para 0)'); console.log('0 === false ->', 0 === false); console.log('"" == 0 ->', '' == 0, '(texto vazio convertido para 0)'); console.log('[] == false ->', [] == false, '(array vazio convertido para texto vazio)'); console.log('null == undefined ->', null == undefined, '(os dois viram 0)'); console.log('null === undefined ->', null === undefined); // A forma de comparar de verdade é deixar os dois lados no mesmo // tipo antes: Number(), String() ou .trim() na ponta. console.log(''); console.log('--- comparar do jeito certo ---'); console.log('Number("0") === 0:', Number('0') === 0); console.log('String(0) === "0":', String(0) === '0'); console.log('" 10 ".trim() === "10":', ' 10 '.trim() === '10'); console.log('"a" !== "A":', 'a' !== 'A', '(maiúscula e minúscula são textos diferentes)'); // Texto é comparado por ordem de caractere, não por valor de // número, mesmo quando o texto parece um número. console.log(''); console.log('--- ordem de comparação ---'); console.log('"10" < "9":', '10' < '9', '(o 1 vem antes do 9 na ordem dos caracteres)'); console.log('10 < 9:', 10 < 9, '(aqui são números)'); console.log('"a" < "b":', 'a' < 'b'); console.log('"a" < "B":', 'a' < 'B', '(maiúscula vem antes de minúscula)'); console.log("'Ana' <= 'Bia':", 'Ana' <= 'Bia'); // Operador unário é o que pega UM valor, em vez de dois. console.log(''); console.log('--- operadores unários ---'); console.log('!true =', !true, '(! nega o booleano)'); console.log('!0 =', !0, '(0 é um valor falso, então !0 é true)'); console.log('!!"texto" =', !!'texto', '(dupla negação entrega o booleano)'); console.log('!!0 =', !!0); console.log('-5 =', -5, '(- nega o número)'); console.log('typeof "x" =', typeof 'x', '(typeof pergunta o tipo)'); console.log('void 0 =', void 0, '(void sempre devolve undefined)'); // `delete` remove a propriedade de um objeto e devolve true ou // false dizendo se conseguiu. console.log(''); console.log('--- delete ---'); const ficha = { nome: 'Ana', idade: 30, cidade: 'Recife' }; console.log('antes:', ficha); console.log('delete ficha.idade devolve:', delete ficha.idade); console.log('depois:', ficha); console.log('delete ficha.inexistente devolve:', delete ficha.inexistente, '(false: não havia o que remover)');
Saída real
--- aritmética ---
10 + 3 = 13
10 - 3 = 7
10 * 3 = 30
10 / 3 = 3.3333333333333335 (divisão real, sempre com casas decimais)
10 % 3 = 1 (resto da divisão)
-7 % 3 = -1 (o resto leva o sinal do dividendo)
2 ** 10 = 1024 (potência)
17 / 5 = 3.4 | 17 % 5 = 2 | o mesmo corte
--- precedência ---
2 + 3 * 4 = 14
(2 + 3) * 4 = 20
2 ** 3 ** 2 = 512 (da direita para a esquerda)
20 - 5 - 3 = 12 (subtração é da esquerda para a direita)
--- o + que muda de sentido ---
1 + 1 + "2" = 22
"1" + 1 + 1 = 111
"Resultado: " + 10 / 2 = Resultado: 5 (a divisão roda antes do +)
String(10 / 2) + "" = 5
--- == contra === ---
"0" == 0 -> true (texto convertido para número)
"0" === 0 -> false (tipos diferentes, não são o mesmo valor)
0 == false -> true (false convertido para 0)
0 === false -> false
"" == 0 -> true (texto vazio convertido para 0)
[] == false -> true (array vazio convertido para texto vazio)
null == undefined -> true (os dois viram 0)
null === undefined -> false
--- comparar do jeito certo ---
Number("0") === 0: true
String(0) === "0": true
" 10 ".trim() === "10": true
"a" !== "A": true (maiúscula e minúscula são textos diferentes)
--- ordem de comparação ---
"10" < "9": true (o 1 vem antes do 9 na ordem dos caracteres)
10 < 9: false (aqui são números)
"a" < "b": true
"a" < "B": false (maiúscula vem antes de minúscula)
'Ana' <= 'Bia': true
--- operadores unários ---
!true = false (! nega o booleano)
!0 = true (0 é um valor falso, então !0 é true)
!!"texto" = true (dupla negação entrega o booleano)
!!0 = false
-5 = -5 (- nega o número)
typeof "x" = string (typeof pergunta o tipo)
void 0 = undefined (void sempre devolve undefined)
--- delete ---
antes: { nome: 'Ana', idade: 30, cidade: 'Recife' }
delete ficha.idade devolve: true
depois: { nome: 'Ana', cidade: 'Recife' }
delete ficha.inexistente devolve: true (false: não havia o que remover)