Dia 4 — Strings

Informatica · Conteudo · publicado em 05/10/2026
Aula 1

Concatenar, interpolar e medir

Concatenar, interpolar e medir

Texto em JavaScript é string, e string tem uma diferença em relação aos outros

tipos: ela é usada para duas coisas ao mesmo tempo — dado e elemento de tela. O

nome que aparece no Text do React Native é uma string; o valor que se compara

para validar um campo também é. Por isso os métodos de string são os mais

chamados do app inteiro.

typeof 'Ana' devolve 'string', e typeof string é o typeof string, e uma string vazia é '' — ela tem

comprimento zero e vale false quando testada. Esse é o teste de campo

obrigatório mais usado:

if (nome.trim() === '') { /* campo vazio */ }

O exemplo confirma essa parte com string vazia: 0 | typeof: string: o

comprimento de '' é 0, e o tipo continua sendo string. Comprimento zero é o

que separa "campo vazio" de "campo com texto" — e o trim antes do teste é o

que separa "campo vazio" de "campo só com espaços".

Concatenar com + e interpolar com crase

Concatenar é juntar strings com o operador +. Interpolação é a forma melhor, e o que o professor chama de template literal

usa a crase (o acento grave) em vez de aspa:

const nome = 'Ana';
const idade = 30;

// concatenar
const texto1 = 'Ola, ' + nome + '!';
console.log(texto1);          // Ola, Ana!

// interpolar
const texto2 = `Ola, ${nome}!`;
console.log(texto2);          // Ola, Ana!

O ${ } é o que separa a crase da aspa: dentro das chaves vai **expressão de

JavaScript**, não só texto. ${idade + 1} funciona, ${tarefas.length} também.

Usar crase em toda stringInterpolation evita a concatenação por + e mantém

código no lugar certo — o que importa em React Native é que string com +

misturado dentro de JSX costuma virar erro de sintaxe.

+ só concatena se algum lado for string; ${ } converte o que colocar dentro,

inclusive objeto. ${ } é o que transforma um número em texto na tela:

const total = 42;
console.log(`${total}`);      // '42' string

O exemplo mostra a conversão com o typeof dos dois lados:

typeof do numero: number | typeof interpolado: string. A mesma idade vale

30 como número e sai '30' como string depois de passar pela crase — e é essa

mudança de tipo que a tela precisa, porque o React Native desenha texto, não

número.

E os três usos do ${ } aparecem em três linhas, uma por tipo de conteúdo:

Ola, ${nome}! para a variável, no ano que vem fara ${idade + 1} anos para a

expressão aritmética, e tarefas: ${tarefas.length} para o método. A segunda é

a que mais importa, porque mostra que dentro das chaves pode ficar uma conta

inteira — ${idade + 1} devolve 31, com idade valendo 30.

Medir: length e os três trimmed

.length devolve o número de caracteres, e só existe em string e array:

console.log('Ana'.length);              // 3
console.log('Ana'.trimStart().length);  // 3

trim apaga os espaços do começo e do fim. trimStart apaga só do começo e

trimEnd só do fim. Os três devolvem uma string nova — a original não muda,

porque string é imutável. Nenhum dos três apaga espaço no meio do texto.

O lugar de trim no app é o campo que o usuário preenche. ' Ana ' não é igual

a 'Ana', e comparar os dois dá false sem motivo visível na tela:

console.log('  Ana  '.trim() === 'Ana');   // true

O exemplo usa JSON.stringify para mostrar os espaços, porque o console

imprimiria ' Ana ' sem diferença visível — as aspas seriam a única pista. A

linha antes: " Ana " | depois: "Ana" mostra os dois lados do trim, e a

seguinte confirma a meia-regra com espaco no meio continua: " Ana Silva ": o

espaço entre o nome e o sobrenome continua lá, porque trim só mexe nas

pontas. A última do grupo é campo vazio apos trim: true — ' ' só com

espaços vira string vazia depois do trim, e é essa linha que implementa o teste

do começo da página.

Mudar caixa e cortar as pontas

toUpperCase e toLowerCase devolvem o texto em maiúsculas ou minúsculas. Como

string é imutável, o resultado precisa ser guardado — chamar o método e ignorar

o retorno não muda nada:

console.log('Ana'.toUpperCase());   // ANA
console.log('Ana'.toLowerCase());   // ana

let nome = 'Ana';
nome.toUpperCase();
console.log(nome);                  // Ana: o retorno foi descartado

nome = nome.toUpperCase();
console.log(nome);                  // ANA

O exemplo da página faz a demonstração do descarte em duas linhas, e o console

distingue as duas: descartando o retorno: Ana e, depois da atribuição,

guardando o retorno: ANA. A variável usuario é a mesma nas duas linhas — o

que muda é se o retorno do método foi usado.

Esse detalhe é a origem de um bug frequente em campo de busca: map com

toLowerCase() sem atribuir de volta não filtra nada, porque cada item

continua com o valor antigo.

O par final fecha com o filtro. As duas linhas do exemplo saem iguais —

filtrando sem atribuar: 2 e filtrando e guardando: 2 item — porque o

filter devolve uma lista nova e o .length conta os itens dessa lista. A

lista tarefas do arquivo tem três itens, e dois deles são 'treinar': é esse

2 que o filtro encontra, e não três.

O que o par demonstra é que toLowerCase() dentro do filter já resolve a

comparação, sem precisar atribuir nada de volta: o filter usa o resultado da

condição, e o item original continua intacto. O descarte só é problema quando o

método é chamado para guardar o resultado — como no toUpperCase acima.

Exemplo

// Concatenar, interpolar com crase, e medir string.
const nome = 'Ana';
const idade = 30;
const tarefas = ['estudar', 'treinar', 'treinar'];

// concatenar com +
console.log('concatenar:', 'Ola, ' + nome + '!');

// interpolar com crase: o ${ } recebe expressao, nao so texto
console.log('interpolar:', `Ola, ${nome}!`);
console.log('expressao dentro:', `no ano que vem fara ${idade + 1} anos`);
console.log('metodo dentro:', `tarefas: ${tarefas.length}`);

// typeof mostra que o resultado da interpolacao e string
console.log('typeof do numero:', typeof idade, '| typeof interpolado:', typeof `${idade}`);

// medir
console.log('length:', 'Ana'.length, '| nome completo:', `${nome} Silva`.length);
console.log('string vazia:', ''.length, '| typeof:', typeof '');

// trim apaga espaco do comeco e do fim, mas nao do meio
const sujo = '  Ana  ';
console.log('antes:', JSON.stringify(sujo), '| depois:', JSON.stringify(sujo.trim()));
console.log('trimStart:', JSON.stringify('  Ana'.trimStart()), '| trimEnd:', JSON.stringify('Ana  '.trimEnd()));
console.log('espaco no meio continua:', JSON.stringify(' Ana Silva '.trim()));
console.log('campo vazio apos trim:', '  '.trim() === '');

// caixa
console.log('toUpperCase:', 'Ana'.toUpperCase(), '| toLowerCase:', 'ANA'.toLowerCase());

// string e imutavel: o metodo devolve uma string nova
let usuario = 'Ana';
usuario.toUpperCase();
console.log('descartando o retorno:', usuario);

usuario = usuario.toUpperCase();
console.log('guardando o retorno:', usuario);

// o bug do filtro sem atribuir de volta
console.log('filtrando sem atribuir:', tarefas.filter((t) => t.toLowerCase() === 'treinar').length);
console.log('filtrando e guardando:', tarefas.filter((t) => t.toLowerCase() === 'treinar').length, 'item');

Saída real

concatenar: Ola, Ana!
interpolar: Ola, Ana!
expressao dentro: no ano que vem fara 31 anos
metodo dentro: tarefas: 3
typeof do numero: number | typeof interpolado: string
length: 3 | nome completo: 9
string vazia: 0 | typeof: string
antes: "  Ana  " | depois: "Ana"
trimStart: "Ana" | trimEnd: "Ana"
espaco no meio continua: "Ana Silva"
campo vazio apos trim: true
toUpperCase: ANA | toLowerCase: ana
descartando o retorno: Ana
guardando o retorno: ANA
filtrando sem atribuir: 2
filtrando e guardando: 2 item
Aula 2

Fatiar, buscar e converter

Fatiar, buscar e converter

Buscar dentro de texto e cortar texto são as duas operações que aparecem em

toda tela que mostra uma lista. includes responde a pergunta booleana,

indexOf responde onde está, slice corta, split quebra em partes.

Onde o texto está

ChamadaDevolveExemplo
texto.includes('an')true/false'Ana'.includes('An')
texto.indexOf('n')a posição, ou -1'Ana'.indexOf('n')
texto.startsWith('An')true/falseprefixo
texto.endsWith('na')true/falsesufixo
texto.charAt(0)o caractere da posição'Ana'.charAt(0)

O -1 do indexOf é o detalhe a guardar: é ele que significa "não está". Não

existe undefined como resposta, o que torna o indexOf pouco prático para

testar e muito prático para cortar.

O exemplo usa 'cachorro' como texto e mostra as cinco chamadas da tabela em

quatro linhas. A primeira sai includes: true | indexOf: 5 — o 'rr' está na

posição 5 do texto, e o includes é a mesma pergunta já respondida. A segunda

é a do -1: nao encontrado devolve -1: -1, o z não existe em 'cachorro'.

A terceira confere as pontas, startsWith: true | endsWith: true. A quarta

compara as duas formas de pegar um caractere: charAt: c | indice direto: a —

'cachorro'[1] é a, e o charAt(1) daria a mesma coisa.

A linha seguinte aplica os mesmos dois métodos em array, e o resultado é

includes em array: true | indexOf: 1. O 1 é a posição do 'b' na lista

['a', 'b'], e não um booleano: o mesmo nome de método devolve posição nos

dois tipos, porque no array os itens também são uma sequência indexada.

includes aceita também array, com o mesmo sentido: devolve true se o elemento

está na lista.

A última linha do exemplo mede o comprimento de dois nomes e sai

length de Ana: 3 | length de José: 4. 'José' tem quatro caracteres e 'Ana'

tem três — o acento não soma unidade, porque é é um caractere só. A contagem é

por unidade de código UTF-16, e é o que o JavaScript chama de caractere: um

nome com acento vale o número de letras que se veem.

Onde a contagem engana é em caractere fora do plano básico, como emoji: eles

ocupam duas unidades e o .length devolve um número maior que o número de

símbolos desenhados. Num campo que limita tamanho de texto, o limite vale a

contagem do JavaScript, e é ela que o usuário vê preenchendo o campo.

Fatiar com slice e cortar em partes com split

slice(inicio, fim) devolve um pedaço, e o fim é exclusivo — a posição fim

não entra. Índice negativo conta de trás: slice(-3) são os três últimos.

console.log('cachorro'.slice(0, 3));    // 'cac'
console.log('cachorro'.slice(-3));      // 'rro'

substring(inicio, fim) é parecido com slice, com uma diferença que já causou

bug: os argumentos invertidos são tolerados. 'cachorro'.substring(3, 1) devolve

'ac' — o substring troca os dois argumentos e fatia do menor para o maior, em

vez de devolver vazio. slice devolve string vazia nesse caso. Para código novo,

slice.

O exemplo põe os dois lado a lado numa linha só:

substring tolera os argumentos invertidos: ac | slice nao: "". O ac ao lado

do "" é a demonstração inteira — mesmo texto, mesmos argumentos, dois

comportamentos. O JSON.stringify existe ali para que a string vazia apareça

com as aspas e não desapareça da linha.

split quebra a string e devolve um array:

console.log('a, b, c'.split(', '));   // ['a', 'b', 'c']
console.log('ana.silva'.split('.'));  // ['ana', 'silva']

O par split e join é o que converte entre os dois tipos:

console.log(['a', 'b', 'c'].join(', '));   // 'a, b, c'

O exemplo confirma o par nas duas direções numa linha:

split: [ 'a', 'b', 'c' ] | join: a, b, c. Do lado do split o console

desenha o array com colchetes e aspas; do lado do join, o texto solto. A

linha seguinte usa o ponto como separador, e `split por ponto: [ 'ana',

'silva' ]` mostra que o separador pode ser qualquer caractere.

O detalhe do split que aparece no app é o do separador com espaço: ele tem que

ser exatamente o mesmo que está no texto. 'a, b, c'.split(', ') acha três

partes; 'a,b,c'.split(',') também; 'a, b, c'.split(',') devolve três partes

iguais com espaço na ponta, e é aí que nasce o item ' b' que não bate com

nenhum filtro.

Substituir

replace substitui só a primeira ocorrência; replaceAll substitui todas.

Como replace não repete, a diferença entre os dois é o número de substituições,

não a forma:

console.log('a-b-c'.replace('-', '+'));      // 'a+b-c'
console.log('a-b-c'.replaceAll('-', '+'));  // 'a+b+c'

Com dois argumentos, o segundo pode ser função, chamada uma vez por ocorrência:

console.log('a1b2'.replace(/\d/g, (d) => '#'));   // 'a#b#'

A expressão regular (/\d/g) é o que substitui em todas as posições sem precisar

de replaceAll.

O exemplo mostra as três formas em três linhas, e a diferença aparece na segunda

palavra de cada uma: replace so a primeira: a+b-c deixa os dois hífens que

sobraram, replaceAll todas: a+b+c troca todos, e replace com funcao: a#b#

troca os dois dígitos. A terceira linha usa o g da expressão regular, que é o

que significa "global" — sem ele, /\d/ pararia na primeira posição.

A função no segundo argumento é chamada uma vez por ocorrência e o que ela

devolve é o texto que substitui. Com (digito) => '#' o nome do parâmetro não é

usado, porque a substituição é sempre #; um caso em que o nome importa é

dobrar o dígito, (d) => String(Number(d) * 2).

Converter texto em número

Number() converte a string inteira. Espaco nas pontas é ignorado, e texto que

não é número dá NaN — inclusive '', que dá 0.

console.log(Number(' 12 '));    // 12
console.log(Number('abc'));     // NaN
console.log(Number(''));        // 0

parseInt para no primeiro caractere que não é número, e o segundo argumento é a

base:

console.log(parseInt('12px'));        // 12
console.log(parseInt('ff', 16));       // 255

parseFloat para no ponto e devolve o número com casas decimais. String() faz o

caminho inverso e converte qualquer valor em string.

console.log(parseFloat('3.5abc'));    // 3.5
console.log(String(42) + ' anos');    // '42 anos'

Em campo de formulário, a combinação que funciona é trim e depois Number, com

a checagem Number.isNaN — porque '' vira 0, e zero não é o que o campo

vazio queria dizer:

function paraNumero(texto) {
  const limpo = String(texto).trim();
  if (limpo === '') return null;
  const numero = Number(limpo);
  return Number.isNaN(numero) ? null : numero;
}

A função é o exemplo inteiro e o console a chama três vezes:

campo vazio: null | valido: 30 | invalido: null. O trim vem antes de tudo,

o campo só com espaços vira '' e sai null pelo primeiro return — antes de

qualquer conversão. ' 30 ' passa pelo trim, vira 30 no Number e sai

30. 'abc' vira NaN, é pego pelo Number.isNaN e sai null.

O null da primeira e da terceira posição é o mesmo valor e não é

undefined. A diferença importa porque o null é uma resposta explícita de

"isto não é número", e o undefined seria "não sei". O campo numérico do

TextInput devolve string sempre, mesmo vazio — e sem essa conversão, a

primeira e a terceira posição chegariam à tela como 0 e NaN.

padStart e padEnd: alinhar na tela

Quando o número vem da API e precisa de casa fixa, padStart completa pela

esquerda com um caractere até o tamanho pedido:

console.log('42'.padStart(5, '0'));    // '00042'
console.log('Ana'.padEnd(5, '.'));     // 'Ana..'

É o método que resolve número de telefone, de documento e de horário em lista

ordenada por texto.

O exemplo fecha com padStart: 00042 | padEnd: Ana.. — cinco posições em ambos

os casos, preenchidas por lados opostos. O padStart é o que alinha coluna de

número; o padEnd alinha coluna de nome. O detalhe é que o segundo argumento é

o caractere de preenchimento e ele pode ser mais de um, ou uma string inteira

qualquer.

E o padStart só completa se faltar: '00042'.padStart(5, '0') devolve

'00042', sem mudar, porque o tamanho já foi atingido. Ele nunca corta texto

longo — para isso é o slice.

Exemplo

// Fatiar, buscar e converter texto.
const texto = 'cachorro';

/* onde o texto esta */
console.log('includes:', texto.includes('rr'), '| indexOf:', texto.indexOf('rr'));
console.log('nao encontrado devolve -1:', texto.indexOf('z'));
console.log('startsWith:', texto.startsWith('ca'), '| endsWith:', texto.endsWith('ro'));
console.log('charAt:', texto.charAt(0), '| indice direto:', texto[1]);

// includes tambem funciona em array
console.log('includes em array:', ['a', 'b'].includes('b'), '| indexOf:', ['a', 'b'].indexOf('b'));

/* fatiar: o fim e exclusivo */
console.log('slice(0, 3):', texto.slice(0, 3), '| slice(-3):', texto.slice(-3));
console.log('substring tolera os argumentos invertidos:', texto.substring(3, 1), '| slice nao:', JSON.stringify(texto.slice(3, 1)));

/* split e join: o par que converte entre os dois tipos */
console.log('split:', 'a, b, c'.split(', '), '| join:', ['a', 'b', 'c'].join(', '));
console.log('split por ponto:', 'ana.silva'.split('.'));

/* substituir */
console.log('replace so a primeira:', 'a-b-c'.replace('-', '+'));
console.log('replaceAll todas:', 'a-b-c'.replaceAll('-', '+'));
console.log('replace com funcao:', 'a1b2'.replace(/\d/g, (digito) => '#'));

/* converter */
console.log('Number:', Number(' 12 '), '| Number de texto:', Number('abc'), '| Number de vazio:', Number(''));
console.log('parseInt:', parseInt('12px'), '| parseInt base 16:', parseInt('ff', 16));
console.log('parseFloat:', parseFloat('3.5abc'), '| String:', String(42) + ' anos');

/* o cuidado do campo de formulario: '' vira 0 */
function paraNumero(campo) {
  const limpo = String(campo).trim();
  if (limpo === '') return null;
  const numero = Number(limpo);
  return Number.isNaN(numero) ? null : numero;
}
console.log('campo vazio:', paraNumero('  '), '| valido:', paraNumero(' 30 '), '| invalido:', paraNumero('abc'));

/* padStart e padEnd alinham na tela */
console.log('padStart:', '42'.padStart(5, '0'), '| padEnd:', 'Ana'.padEnd(5, '.'));

/* nome com acento e comprimento em UTF-16 */
console.log('length de Ana:', 'Ana'.length, '| length de José:', 'José'.length);

Saída real

includes: true | indexOf: 5
nao encontrado devolve -1: -1
startsWith: true | endsWith: true
charAt: c | indice direto: a
includes em array: true | indexOf: 1
slice(0, 3): cac | slice(-3): rro
substring tolera os argumentos invertidos: ac | slice nao: ""
split: [ 'a', 'b', 'c' ] | join: a, b, c
split por ponto: [ 'ana', 'silva' ]
replace so a primeira: a+b-c
replaceAll todas: a+b+c
replace com funcao: a#b#
Number: 12 | Number de texto: NaN | Number de vazio: 0
parseInt: 12 | parseInt base 16: 255
parseFloat: 3.5 | String: 42 anos
campo vazio: null | valido: 30 | invalido: null
padStart: 00042 | padEnd: Ana..
length de Ana: 3 | length de José: 4