Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

Métodos de string em JavaScript: os essenciais

Recortar com slice, trocar com replaceAll, quebrar com split e alinhar com padStart — cada método rodado no Node, com a saída real e a pegadinha de cada.

Rodolfo Mori8 min de leitura

Todo dado que chega de um formulário, de uma API ou de um arquivo CSV chega como string. Antes de virar número, data ou registro no banco, ele passa por dois ou três métodos de string — e é aí que o bug nasce.

A resposta curta: para recortar use slice, para trocar use replaceAll, para quebrar use split, para completar com zeros use padStart, e para limpar o que veio do usuário use trim com toLowerCase. Nenhum deles altera a string original: todos devolvem uma string nova.

Os exemplos são de uma loja: SKU, descrição de produto, aviso de frete, número de pedido. Se você ainda não passou por variáveis em JavaScript, vale ler antes — aqui tudo é const.

Strings são imutáveis: nenhum método altera os caracteres do valor original. Em palavras simples, cada recorte, troca ou limpeza produz outra string, e você precisa guardar esse retorno se quiser usá-lo depois.

A bancada de recorte sempre devolve outra fita

Imagine uma loja cortando uma cópia de fita adesiva com o código do produto. A fita original permanece no rolo; o pedaço cortado é outro material que precisa ser recolhido. slice, replace, trim e os demais métodos funcionam assim: leem a string atual e devolvem um novo valor, sem editar o anterior no lugar.

Antes do primeiro exemplo, anote o valor original e o retorno esperado de cada método. Depois imprima os dois lado a lado. Repita uma chamada sem atribuir o retorno e confira qual variável mudou. Essa microprática torna a imutabilidade visível no console.

slice(inicio, fim) devolve o pedaço entre os dois índices, com o fim exclusivo. O índice começa em zero.

js
const produto = 'Teclado mecânico ABNT2';

console.log(produto.length);
console.log(produto.slice(0, 7));
console.log(produto.slice(-5));
console.log(produto.substring(0, 7));
console.log(produto.substring(7, 0));
console.log(produto.slice(7, 0));
22 Teclado ABNT2 Teclado Teclado

As quatro primeiras linhas fazem o esperado. As duas últimas são a diferença entre os dois métodos: com os argumentos invertidos, substring troca os dois de lugar sem avisar e devolve 'Teclado' de novo, enquanto slice devolve string vazia — a última linha da saída está em branco.

Fica mais claro imprimindo com aspas:

js
const produto = 'Teclado mecânico ABNT2';

console.log(JSON.stringify(produto.substring(7, 0)));
console.log(JSON.stringify(produto.slice(7, 0)));
"Teclado" ""

A segunda diferença é o índice negativo. slice conta a partir do fim; substring trata qualquer negativo como zero:

js
const sku = 'TEC-MEC-ABNT2-001';

console.log(sku.slice(-3));
console.log(sku.substring(-3));
console.log(sku.at(-1));
console.log(sku.charAt(sku.length - 1));
001 TEC-MEC-ABNT2-001 1 1

Pegar os três últimos caracteres de um SKU é uma tarefa de todo dia. Com substring você recebe a string inteira e não percebe até o pedido errado sair.

Existe ainda o substr, que recebe posição e quantidade em vez de dois índices. Ele funciona, mas é um anexo legado da especificação, mantido só por compatibilidade com a web antiga:

js
const sku = 'TEC-MEC-ABNT2-001';

console.log(sku.substr(4, 3));
console.log(sku.slice(4, 4 + 3));
MEC MEC

Achar antes de recortar

Recortar por índice fixo só serve quando o formato é fixo. No resto dos casos você primeiro procura:

js
const descricao = 'Teclado mecânico ABNT2 com switch azul e cabo USB-C';

console.log(descricao.indexOf('switch'));
console.log(descricao.indexOf('Switch'));
console.log(descricao.includes('switch'));
console.log(descricao.toLowerCase().includes('switch'));
console.log(descricao.startsWith('Teclado'));
console.log(descricao.endsWith('USB-C'));
27 -1 true true true true

indexOf devolve -1 quando não acha — e -1 é um número verdadeiro em condição booleana. if (descricao.indexOf('Switch')) entra no if justamente quando não achou. Use includes quando a pergunta é “tem ou não tem”, e guarde indexOf para quando você precisa da posição.

Repare também que a busca diferencia maiúscula de minúscula. Comparação de texto digitado por gente sempre passa por toLowerCase dos dois lados.

replace troca uma; replaceAll troca todas

Esta é a pegadinha que mais aparece em code review de iniciante:

js
const aviso = 'frete grátis em SP, frete grátis em RJ, frete grátis em MG';

console.log(aviso.replace('frete grátis', 'frete fixo'));
console.log(aviso.replaceAll('frete grátis', 'frete fixo'));
console.log(aviso.replace(/frete grátis/g, 'frete fixo'));
frete fixo em SP, frete grátis em RJ, frete grátis em MG frete fixo em SP, frete fixo em RJ, frete fixo em MG frete fixo em SP, frete fixo em RJ, frete fixo em MG

Com um texto simples como primeiro argumento, replace troca a primeira ocorrência e para. Não é bug: é a especificação. As duas formas de trocar tudo são replaceAll ou uma expressão regular com a flag g.

Misturar as duas é erro em tempo de execução, e de propósito:

js
const aviso = 'frete grátis em SP, frete grátis em RJ';

console.log(aviso.replaceAll(/frete grátis/, 'frete fixo'));
file:///private/tmp/loja/aviso-frete.mjs:3 console.log(aviso.replaceAll(/frete grátis/, 'frete fixo')); ^

TypeError: String.prototype.replaceAll called with a non-global RegExp argument at String.replaceAll (<anonymous>) at file:///private/tmp/loja/aviso-frete.mjs:3:19 Node.js v24.16.0

A linguagem prefere parar a te deixar achar que trocou tudo. É uma das poucas vezes em que um TypeError é um favor.

Tem mais uma armadilha, e essa é silenciosa: a string de substituição tem sintaxe própria. $& significa “o trecho encontrado”, e $$ significa um cifrão literal:

js
const nome = 'mouse sem fio';

console.log(nome.replace('mouse', '$& gamer'));
console.log(nome.replace('mouse', '$$ gamer'));
console.log(nome.replace('mouse', () => '$& gamer'));
mouse gamer sem fio $ gamer sem fio $& gamer sem fio

Se o texto que você está inserindo vem de fora — um preço R$ 99,00, por exemplo — passe uma função como segundo argumento. Dentro dela o retorno é literal, sem interpretação de $.

split e join: da linha do arquivo ao array

split quebra a string num array usando um separador; join faz o caminho de volta.

js
const linha = 'Teclado mecânico;289,90;2';

const [nome, preco, quantidade] = linha.split(';');
console.log(nome, preco, quantidade);
console.log(linha.split(';', 2));
console.log(['Teclado', 'Mouse', 'Headset'].join(' + '));
Teclado mecânico 289,90 2 [ 'Teclado mecânico', '289,90' ] Teclado + Mouse + Headset

O segundo argumento de split é um limite de itens, não a posição onde parar de ler. Ele descarta o resto em vez de juntar — quase nunca é o que você quer num CSV.

Onde split('') mente: acento e emoji

Separador vazio parece a forma óbvia de “pegar caractere por caractere”. Ela funciona para texto ASCII e quebra em tudo que o Brasil escreve:

js
const cliente = 'Ana Café 🛒';

console.log(cliente.length);
console.log(cliente.split(''));
console.log([...cliente]);
console.log([...cliente].length);
11 [ 'A', 'n', 'a', ' ', 'C', 'a', 'f', 'é', ' ', '\ud83d', '\uded2' ] [ 'A', 'n', 'a', ' ', 'C', 'a', 'f', 'é', ' ', '🛒' ] 10

O carrinho virou dois pedaços inválidos. length e split('') trabalham em unidades de código UTF-16, e um emoji ocupa duas. O spread ([...texto]) e o Array.from percorrem por ponto de código e devolvem o emoji inteiro.

O acento tem um problema parecido, e mais sorrateiro, porque os dois textos parecem idênticos na tela:

js
const composto = 'Café';
const decomposto = 'Café';

console.log(composto, decomposto);
console.log(composto.length, decomposto.length);
console.log(composto === decomposto);
console.log(decomposto.split(''));
console.log(composto === decomposto.normalize('NFC'));
Café Café 4 5 false [ 'C', 'a', 'f', 'e', '́' ] true

Duas strings visualmente iguais, comprimentos diferentes, comparação false. Isso acontece de verdade: macOS costuma entregar nomes de arquivo decompostos, e formulário colado do Word vem misturado. Se você compara texto com acento, normalize('NFC') dos dois lados antes.

Para busca em catálogo, o caminho é decompor e jogar fora os acentos:

js
function normalizar(texto) {
  return texto
    .trim()
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .toLowerCase();
}

const catalogo = ['Teclado mecânico ABNT2', 'Mouse sem fio', 'Óculos VR', 'Cabo HDMI'];

console.log(normalizar('Teclado mecânico ABNT2'));
console.log(catalogo.filter((p) => normalizar(p).includes(normalizar('  MECANICO '))));
console.log(catalogo.filter((p) => normalizar(p).includes(normalizar('oculos'))));
teclado mecanico abnt2 [ 'Teclado mecânico ABNT2' ] [ 'Óculos VR' ]

NFD separa a letra do acento; o replace apaga a faixa de sinais diacríticos. O cliente digita “oculos” e acha “Óculos VR”.

padStart: número de pedido, cartão e recibo alinhado

padStart(tamanho, preenchimento) completa à esquerda até a string ter o tamanho pedido. padEnd faz o mesmo à direita.

js
const pedido = 47;

console.log(String(pedido).padStart(6, '0'));
console.log(`#${String(pedido).padStart(6, '0')}`);
console.log('5432'.padStart(16, '*'));
console.log('Subtotal'.padEnd(14, '.') + 'R$ 289,90');
console.log('Frete'.padEnd(14, '.') + 'R$  12,50');
000047 #000047 ************5432 Subtotal......R$ 289,90 Frete.........R$ 12,50

Repare no String(pedido): padStart é método de string, e pedido é número. Sem a conversão, o programa quebra — é exatamente o erro da última seção.

trim e a limpeza do que veio do formulário

Espaço no começo, espaço no fim, quebra de linha invisível colada junto: é o estado normal de um campo de texto preenchido por gente.

js
const digitado = '  Ana@CLIENTE.com.br \n';

console.log(JSON.stringify(digitado));
console.log(JSON.stringify(digitado.trim()));
console.log(digitado.trim().toLowerCase());
console.log(JSON.stringify('  PRIMEIRACOMPRA  '.trimStart()));
console.log(JSON.stringify('  PRIMEIRACOMPRA  '.trimEnd()));
" Ana@CLIENTE.com.br \n" "Ana@CLIENTE.com.br" ana@cliente.com.br "PRIMEIRACOMPRA " " PRIMEIRACOMPRA"

JSON.stringify na hora de depurar string é um truque barato e muito útil: ele mostra as aspas e escapa a quebra de linha, então você o espaço que estava lá.

Toda string é imutável

Nenhum método de string altera a original. Todos devolvem uma nova:

js
const cupom = 'primeiracompra';

cupom.toUpperCase();
console.log(cupom);
console.log(cupom.toUpperCase());
primeiracompra PRIMEIRACOMPRA

A segunda linha do código não faz nada: calcula uma string maiúscula e joga fora. Isso não gera erro nem aviso — só um campo que continua minúsculo em produção. Se o resultado importa, ele precisa ser atribuído ou usado na hora.

Erros comuns

O campeão é chamar método de string num valor que não é string. Acontece toda vez que um campo numérico do banco ou do JSON encontra um toUpperCase, padStart ou trim:

js
const pedido = { id: 4821, cupom: 2026 };

console.log(pedido.cupom.toUpperCase());
file:///private/tmp/loja/cupom-numero.mjs:3 console.log(pedido.cupom.toUpperCase()); ^

TypeError: pedido.cupom.toUpperCase is not a function at file:///private/tmp/loja/cupom-numero.mjs:3:26 Node.js v24.16.0

Leia o caminho que o erro imprime: pedido.cupom.toUpperCase. Ele está dizendo que pedido.cupom existe — se não existisse, a mensagem seria Cannot read properties of undefined — e que o valor guardado ali não tem esse método. Um número não tem toUpperCase.

A correção é converter explicitamente:

js
const pedido = { id: 4821, cupom: 2026 };

console.log(String(pedido.cupom).toUpperCase());
console.log(String(pedido.cupom).padStart(8, '0'));
2026 00002026

String(valor) funciona até com null e undefined, devolvendo 'null' e 'undefined'. Se isso for pior do que quebrar, valide antes em vez de converter cego.

Os outros dois erros que se repetem já apareceram acima, e vale reunir:

  • replace com texto simples trocando só a primeira ocorrência, e ninguém percebendo porque o teste tinha uma ocorrência só.
  • método chamado sem atribuir o resultado, por hábito de linguagem em que string é mutável.

Qual método usar em cada situação

preciso método cuidado
pegar um pedaço slice(inicio, fim) fim é exclusivo; negativo conta do fim
pegar o último caractere at(-1) charAt não aceita negativo
saber se contém includes indexOf devolve -1, que é verdadeiro
trocar todas as ocorrências replaceAll replace com texto troca só a primeira
quebrar em array split(separador) split('') estraga emoji e acento decomposto
percorrer caractere a caractere [...texto] length conta unidades UTF-16
completar com zeros padStart(n, '0') converta o número com String() antes
limpar entrada de formulário trim().toLowerCase() e normalize('NFC') se houver acento

O próximo passo natural é o outro lado da mesma moeda: transformar essas strings em números em JavaScript — e descobrir por que '1.234,56' vira NaN. Se quiser ver onde esta lição cai no roteiro inteiro, o guia completo de JavaScript mostra a ordem de estudo, e a trilha de JavaScript lista as lições publicadas em sequência.

  • strings
  • slice
  • replace
  • split
  • padstart

Perguntas frequentes

Qual a diferença prática entre slice e substring?
slice aceita índice negativo e conta a partir do fim; substring trata negativo como zero. E quando você inverte os argumentos, substring troca os dois de lugar em silêncio, enquanto slice devolve string vazia. Use slice e trate substring como legado.
Por que meu replace só trocou a primeira ocorrência?
Porque com um texto simples como primeiro argumento é isso que replace faz, por especificação. Para trocar todas, use replaceAll, ou uma expressão regular com a flag g. replaceAll com regex sem a flag g lança TypeError de propósito, para você não achar que trocou tudo.
Por que texto.length devolve um número maior do que os caracteres que vejo?
length conta unidades de código UTF-16, não caracteres. Emoji ocupa duas unidades e acento decomposto ocupa duas posições. Para contar o que o leitor enxerga, use o spread — [...texto].length — que percorre por ponto de código.
Preciso guardar o retorno de toUpperCase numa variável?
Precisa. String em JavaScript é imutável: nenhum método altera a original, todos devolvem uma string nova. Chamar cupom.toUpperCase() sem atribuir o resultado a nada é uma linha que não faz absolutamente nada.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — String — developer.mozilla.org
  2. MDN — String.prototype.replaceAll() — developer.mozilla.org
  3. ECMAScript 2026 Language Specification — String Objects — tc39.es

Continue por aqui