Devi lavorare con il formato Base64? Allora questo sito è perfetto per te! Usa il nostro praticissimo strumento online per codificare o decodificare i tuoi dati.

Codifica Base64 in JavaScript/Browser: una guida completa

Hai qualcosa che deve viaggiare, e la strada è larga solo per ASCII semplice. Può essere un'immagine che deve stare dentro una risposta JSON, un oggetto di configurazione che deve stare in viaggio dentro un URL, un token i cui tre segmenti sono punti e lettere, un file che un'API pretende arrivi come stringa Base64 dentro un corpo JSON. Il Base64 è il casello per esattamente questa situazione, e la home page di questo sito ha già spiegato il formato - quattro caratteri stampabili che fanno da sostituti per ogni tre byte, con il riempimento = per chiudere il gruppo - quindi ecco l'unico numero da tenere a mente mentre leggi: la codifica è la direzione che fa crescere. Ogni tre byte che consegni tornano come quattro caratteri, una tassa dimensionale di circa il 33 percento, riscossa in banda, archiviazione e memoria. Usa il Base64 quando il canale esige testo stampabile, e sappi esattamente quanto ti costa quella tassa.

La notizia incoraggiante è che il browser ha sempre saputo fare questo lavoro senza un singolo pacchetto. btoa() è a bordo dai primi anni 2000, TextEncoder ha trasformato il tuo testo Unicode vero in byte onesti un decennio fa, e nell'ondata Baseline 2025 la piattaforma ha finalmente aggiunto Uint8Array.toBase64(), che codifica direttamente gli array di byte con un'opzione per l'alfabeto sicuro per URL. Questo articolo è la mappa delle decisioni: quale strumento per quale lavoro, dove si nascondono i bordi affilati (tutti riconducibili allo stesso confine), e le ricette concrete per i luoghi dove ti chiederanno davvero di produrre Base64.

Scegliere il proprio codificatore

Non esiste più un codificatore "quello" definitivo, e prendere quello sbagliato è come nascono i bug classici. La tabella qui sotto è l'intero albero delle decisioni:

Situazione Usa
Testo ASCII semplice, valore occasionale btoa(text)
Testo vero con accenti, emoji, CJK new TextEncoder().encode(text), poi btoa o toBase64
Byte già in un Uint8Array bytes.toBase64() nei browser 2025+, il ponte btoa a blocchi altrove
URL, JWT, nomi di file toBase64({ alphabet: 'base64url', omitPadding: true })
Browser vecchi o un codice condiviso js-base64, o la ricetta classica TextEncoder + btoa

Il modello sotto la tabella: btoa() legge solo caratteri a byte singolo, quindi qualsiasi cosa che non sia ASCII deve prima diventare un array di byte, e quell'array di byte è ciò attorno a cui sono state costruite le API moderne. Tieni a mente "il testo diventa byte, i byte diventano Base64" e ogni ricetta di questo articolo è lo stesso procedimento di due passi con nomi diversi sopra.

btoa e il confine Latin1

btoa(stringToEncode) - da stringa binaria a stringa ASCII - è il codificatore originale, disponibile in ogni browser che conti (Chrome 4, Firefox 1, Safari 3, IE 10 e successivi, tutti gli ambiti dei worker, e Node dalla versione 16). Il suo contratto ha una sola clausola, ed è quella clausola dove tutto va storto: ogni carattere nell'input deve avere un punto di codice tra 0 e 255. La funzione legge punti di codice, non byte UTF-8, quindi "é" (punto di codice 233) passa dritto mentre "你" (punto di codice 20320) lancia un'eccezione DOMException di nome InvalidCharacterError prima che un solo carattere venga codificato. Il confine non è "ASCII", non è "Unicode", è esattamente 256, e include i caratteri di controllo in fondo - codificare un byte NUL è legale e ha senso, ed è una delle ragioni per cui la funzione esiste.

Il comportamento completo, riga per riga:

Input Risultato
"Hello, World!" "SGVsbG8sIFdvcmxkIQ==" - il caso da manuale
"" (stringa vuota) "" - niente in entrata, niente in uscita
"\u0000" (NUL) "AA==" - i caratteri di controllo sono cittadini di prima classe
"a\u00e9z" (é, punto di codice 233) "Yel6" - tutto l'intervallo Latin1 passa
"\u0100" (punto di codice 256) lancia InvalidCharacterError - un passo oltre il confine
"h\u4f60" (你, punto di codice 20320) lancia InvalidCharacterError - e lo stesso vale per ogni emoji, perché sono tutte ben al di sopra di 255

Due note pratiche. Il messaggio di errore varia da motore a motore - Firefox dice "String contains an invalid character", Chrome dice che la stringa "contains characters outside of the Latin1 range" - quindi in qualsiasi codice difensivo catturi per nome dell'eccezione. E l'eccezione scatta sul primo carattere colpevole, non alla fine: btoa non codifica metà della stringa e si scusa. Quando vuoi il comportamento Latin1 di proposito (codificare una stringa di byte costruita apposta con punti di codice 0-255), la funzione fa esattamente quello che hai chiesto, e la tabella qui sopra è l'intera sua personalità.

Il ponte dei byte

Quindi la domanda diventa: come i dati veri - i byte UTF-8 del tuo testo, il contenuto di un file, l'output di una canvas - finiscono nell'input di btoa()? La risposta è il "ponte dei byte": una stringa JavaScript in cui ogni carattere custodisce un valore di byte, lo stesso trucco che i decoder producono e che btoa capisce nativamente. La versione ingenua è un ciclo:

function bytesToBase64 (bytes) {
  let binary = '';
  for (let i = 0; i < bytes.length; i += 1) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

Corretto, ma la concatenazione di stringhe in un ciclo è lenta per i file grandi, e la scorciatoia popolare - String.fromCharCode.apply(null, bytes), che passa l'intero array come argomenti in una sola chiamata - ha una falesia netta. Le chiamate di funzione hanno un limite sul numero di argomenti, e lo si raggiunge ben prima del primo megabyte:

const big = new Uint8Array(1000000);
btoa(String.fromCharCode.apply(null, big));
// RangeError in Firefox: "too many arguments provided for a function call"
// RangeError in Chrome: "Maximum call stack size exceeded"

La correzione che ha salvato più funzioni di upload file di qualsiasi altro singolo cambiamento è attraversare il ponte a blocchi, qualche migliaio di caratteri alla volta, e unire i risultati:

function bytesToBase64Chunked (bytes) {
  const CHUNK = 0x8000;
  const parts = [];
  for (let i = 0; i < bytes.length; i += CHUNK) {
    parts.push(String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK)));
  }
  return btoa(parts.join(''));
}

Ogni blocco è abbastanza piccolo da essere applicato in sicurezza, subarray dà una vista senza copiare, e l'unione produce esattamente la stessa stringa binaria che avrebbe prodotto il ciclo. Ora l'altro lato della medaglia, quello del testo. Per qualsiasi testo vero, TextEncoder - il codificatore UTF-8 della piattaforma, disponibile in Firefox 18, Chrome 38, Safari 10.1 e ovunque da allora - trasforma la tua stringa in byte onesti prima che il ponte faccia il suo lavoro:

const bytes = new TextEncoder().encode('hello 你好');
const base64 = bytesToBase64Chunked(bytes);
console.log(base64); // "aGVsbG8g5L2g5aW9"

Quel risultato è ciò che "hello 你好" è davvero in transito: sei byte ASCII più sei byte UTF-8 per i due caratteri cinesi, tutti con lo stesso travestimento stampabile. Se il tuo testo non è UTF-8 - e sul web, di solito lo è - prima ti serve l'altra codifica di caratteri, il che significa codificarlo da qualche parte che parla quella codifica, di solito il server. TextEncoder si rifiuta deliberatamente di indovinare, ed ha ragione di farlo.

La scorciatoia 2025: Uint8Array.toBase64

Se hai già in mano un Uint8Array, il ponte è una deviazione, perché la nuova funzionalità ECMAScript (ES2026) codifica l'array direttamente: bytes.toBase64(options). È arrivata in Chrome 140, Edge 140, Firefox 133, Safari 18.2, Node 25 e Deno 2.5 - la stessa ondata Baseline 2025 del suo fratello della decodifica - e accetta due opzioni che lo rendono il codificatore più versatile della piattaforma. La prima è alphabet: "base64" (il predefinito) o "base64url". La seconda è omitPadding: impostala su true e i caratteri = finali vengono rimossi, che è la forma che la maggior parte dei consumatori amichevoli per URL vuole. Passare qualcos'altro come opzioni lancia un TypeError, che è l'API che fa il galante sul tuo refuso:

const bytes = new Uint8Array([251, 255]);
console.log(bytes.toBase64()); // "+/8="
console.log(bytes.toBase64({ omitPadding: true })); // "+/8"
console.log(bytes.toBase64({ alphabet: 'base64url' })); // "-_8="

Quei due byte sono scelti per essere massimamente scortesi con l'alfabeto: producono un + e uno / in modalità standard, quindi l'ultima riga mostra esattamente cosa cambia quando passi a base64url. Le prestazioni sono il bonus silenzioso: su un Firefox recente, codificare dieci megabyte richiede circa cinque millisecondi con toBase64, mentre il percorso del ponte di stringhe qui sopra richiede circa quindici volte di più, perché costruisce una stringa intermedia gigante nel processo. Sui browser più vecchi il ponte rimane perfettamente utilizzabile per qualsiasi cosa sotto alcuni megabyte - e la versione a blocchi qui sopra è quella che ti serve, per le ragioni dell'ultima sezione.

Output sicuro per URL

Il Base64 ha una variante dedicata per i luoghi dove +, / e = causano danni, e merita una sezione tutta sua perché tanto codice rotto è solo Base64 standard che ha incontrato un URL. In una stringa di query, + è uno spazio; in un percorso, / è un separatore; e = chiede la codifica percentuale in alcune posizioni. L'alfabeto sicuro per URL e nomi di file della sezione 5 di RFC 4648 - base64url - scambia quei due caratteri con - e _, e dato che la lunghezza dei dati è di solito nota lato ricevente, permette anche di rimuovere del tutto il riempimento. L'output viaggia attraverso URLSearchParams, segmenti di percorso, frammenti e nomi di file senza un solo carattere %.

Con l'API 2025 è un solo oggetto di opzioni:

const params = new URLSearchParams();
params.set('payload', bytes.toBase64({ alphabet: 'base64url', omitPadding: true }));
console.log(params.toString()); // "payload=-_8" - nessuna codifica percentuale

Sui browser più vecchi, converti dopo aver codificato con btoa. Due replace e un trim fanno tutto il lavoro:

function toUrlBase64 (base64) {
  return base64
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}
console.log(toUrlBase64(btoa('hi?/x'))); // "aGk_L3g"

Tre regole tengono pulito il canale. Scegli un alfabeto per canale e fermati lì - un valore che mescola + e - non appartiene a nessuna delle due famiglie, e nessun decoder indovinerà quale intendevi. Il riempimento è un contratto, non un suggerimento: se lo ometti, il ricevente deve essere pronto per un valore senza riempimento, e se lo tieni, il ricevente non deve incepparsi su di esso (i browser sono tolleranti, alcuni schemi JSON no). E ricorda che lo scambio è reversibile e senza perdite: - e _ mappano sulle stesse posizioni 62 e 63 dell'alfabeto occupate da + e /, quindi non si perde nulla scegliendo la coppia più amichevole.

Far viaggiare le immagini: i data URL

L'uso più antico e più visibile del Base64 nel browser è il data URL: data:, un tipo multimediale opzionale, un flag ;base64 opzionale, una virgola, poi il carico utile. I carichi utili di testo sono codificati in forma percentuale; quelli binari - immagini, font, audio - sono Base64, e il browser li renderizza con zero richieste HTTP. Per un file immagine appena scelto dall'utente, il FileReader fa la codifica al posto tuo e ti restituisce l'URL finito:

const reader = new FileReader();
reader.onload = () => {
  console.log(reader.result); // "data:image/png;base64,iVBORw0KGgo..."
  imageElement.src = reader.result;
};
reader.readAsDataURL(file);

Il risultato è un src pronto all'uso, un valore che puoi salvare in localStorage, o inviare in un corpo JSON. Se l'immagine è su una canvas invece - uno screenshot, una foto trattata, un grafico generato - canvas.toDataURL() fa questo lavoro dai primissimi rilasci dei browser, e lascia persino scegliere il formato e, per i formati con perdita, la qualità:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.drawImage(photo, 0, 0);
const pngUrl = canvas.toDataURL('image/png');
const jpegUrl = canvas.toDataURL('image/jpeg', 0.8);

Tre inciampi da mettere in conto. Primo, la regola della canvas contaminata: se hai disegnato un'immagine cross-origin sulla canvas senza permesso CORS, ogni tentativo di rileggere i pixel - incluso toDataURL - lancia un SecurityError. La correzione è caricare l'immagine con crossOrigin = 'anonymous' e assicurarsi che il server invii le intestazioni giuste. Secondo, l'argomento qualità è ignorato per PNG e ha senso solo per JPEG (e WebP) - una fonte comune di "perché il mio PNG è più grande". Terzo, e il più grande: il carico utile è circa il 33 percento più grande del file, e sta nella pagina come stringa. Per le immagini che non lasciano mai il browser, c'è un'alternativa gratuita - un URL di oggetto, che avvolge il Blob senza codificarlo affatto:

const objectUrl = URL.createObjectURL(blob);
imageElement.src = objectUrl;
URL.revokeObjectURL(objectUrl); // quando ne hai finito di usarlo

La divisione del lavoro che ne deriva: URL di oggetto per tutto ciò che resta nella pagina, data URL per tutto ciò che deve essere copiato, salvato o inviato come testo. Entrambi sono di prima classe; risolvono semplicemente problemi diversi.

Costruire e firmare un JWT

Se generi token nel browser - per un flusso di autenticazione self-hosted, una demo o un front end serverless - il formato JWS compatto è tre segmenti base64url: intestazione, carico utile, firma, senza riempimento da nessuna parte. La Web Crypto API gestisce la firma; la codifica è esattamente l'output sicuro per URL di due sezioni fa:

const encoder = new TextEncoder();
const segment = (bytes) =>
  bytes.toBase64({ alphabet: 'base64url', omitPadding: true });
const header = segment(encoder.encode(JSON.stringify({ alg: 'HS256', typ: 'JWT' })));
const payload = segment(encoder.encode(JSON.stringify({ sub: '1234567890', name: 'John Doe' })));
const key = await crypto.subtle.importKey(
  'raw',
  encoder.encode('shared-secret'),
  { name: 'HMAC', hash: 'SHA-256' },
  false,
  ['sign']
);
const signature = segment(
  new Uint8Array(
    await crypto.subtle.sign('HMAC', key, encoder.encode(header + '.' + payload))
  )
);
const token = header + '.' + payload + '.' + signature;

Due dettagli contano più della parte meccanica. La firma copre esattamente header + '.' + payload - i segmenti grezzi, non il JSON - quindi qualsiasi modifica a una delle due parti invalida il token, ed è questo il punto. E crypto.subtle.sign restituisce un ArrayBuffer grezzo, da qui l'avvolgimento in una riga in un Uint8Array prima del codificatore di segmento. Per i token basati su RSA il flusso è identico con RS256 e una coppia di chiavi, e se esporti una chiave pubblica come JWK (crypto.subtle.exportKey('jwk', key)), i membri numerici - n, e, e per le chiavi private d, p, q - escono come base64url senza riempimento automaticamente. Le avvertenze di sicurezza sono le stesse di qualsiasi token: un'intestazione alg: "none" è una richiesta di saltare la verifica, le dichiarazioni di tempo (exp, nbf) devono essere applicate, e un server che accetta sia HMAC sia RSA per lo stesso gruppo di destinatari apre la classica porta della confusione delle chiavi. Codifica correttamente, firma correttamente, verifica lato ricevente.

Intestazioni di autenticazione

Lo schema di autenticazione più semplice sul web è anche il più istruttivo su cosa sia e non sia il Base64. L'HTTP Basic invia Authorization: Basic seguito dal Base64 di username:password - una chiamata, nessun ponte dei byte necessario, perché nomi utente e password sono (si spera) testo semplice:

const credentials = btoa('alice:secret123');
fetch('/api/me', {
  headers: { Authorization: 'Basic ' + credentials }
});
// Authorization: Basic YWxpY2U6c2VjcmV0MTIz

Ed ecco la lezione che sta in una riga: il Base64 non è crittografia. L'intestazione qui sopra è a una chiamata atob da alice:secret123 - per l'attaccante e per chiunque legga i log - quindi l'autenticazione Basic è accettabile solo su HTTPS, dove il trasporto è la protezione vera e il Base64 è solo la formattazione. Per qualsiasi cosa con una vita più lunga di una singola richiesta, preferisci gli schemi basati su token: un token Bearer è anche lui un'unica intestazione, ma è un valore casuale il cui segreto non deve mai essere portato nell'intestazione, e può essere revocato. La scelta di codifica tra i due è banale - entrambi sono btoa o testo semplice - ma la scelta di sicurezza no, e va fatta di proposito.

File in ingresso, testo in uscita

Gli upload sono dove la tassa del 33 percento viene quotata in soldi veri, perché il file è di solito la cosa più grande nella pagina. Ci sono due strade, e la prima è quella da prendere di default: i dati di forma multipart. FormData porta il file come byte grezzi in un corpo standard, con il browser che fa l'inquadratura, e il Base64 non c'è da nessuna parte nel quadro - nessuna tassa dimensionale, nessuna stringa intermedia, e i byte scorrono al server man mano che vengono letti:

const form = new FormData();
form.append('upload', file);
await fetch('/api/upload', { method: 'POST', body: form });

La seconda strada è per le API che insistono per un corpo JSON con il file come stringa - alcune funzioni serverless, alcuni backend mobili, alcuni servizi legacy. Lì la codifica è una riga per file, e il costo è esattamente ciò che dice la tassa: un file da 5 megabyte diventa una stringa da 6,7 megabyte, che poi viene serializzata in JSON, che poi viene inviata. Va bene per una foto, doloroso per un video:

const bytes = new Uint8Array(await file.arrayBuffer());
const body = JSON.stringify({
  name: file.name,
  content: bytes.toBase64()
});
await fetch('/api/upload-json', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body
});

Per i file grandi su quella strada, non costruire una stringa gigante in una singola chiamata - costruiscila a frammenti, dove ogni frammento è un multiplo di tre byte. Quell'allineamento è ciò che rende legale il trucco: un multiplo di tre byte codifica in un multiplo pulito di quattro caratteri senza riempimento, quindi i frammenti codificati indipendentemente si concatenano nell'esatta codifica dell'intero file, e solo l'ultimo frammento porta mai il riempimento:

async function encodeLargeFile (file) {
  const bytes = new Uint8Array(await file.arrayBuffer());
  const SLICE = 3 * 1000 * 1000;
  const parts = [];
  for (let i = 0; i < bytes.length; i += SLICE) {
    parts.push(bytes.subarray(i, i + SLICE).toBase64());
  }
  return parts.join('');
}

La stessa idea di allineamento è il motivo per cui non dovresti mai spezzare una stringa Base64 in un punto arbitrario e aspettarti che i pezzi si decodifichino da soli - un gruppo di tre byte è l'atomo, e un taglio in mezzo a uno lascia un frammento pendente. I download sono l'immagine speculare: per un file generato, uno piccolo può uscire attraverso un data URL in un link di download, ma per qualsiasi cosa consistente un Blob più un URL di oggetto è la strada sana, perché il browser non deve mai portare l'intero carico utile come stringa, per cominciare.

Salvare e condividere lo stato

Altre due strade solo testuali dove il Base64 fa lavoro vero. La prima è l'archiviazione: localStorage e sessionStorage contengono stringhe, quindi i dati strutturati o binari vengono codificati prima di entrarci. L'andata e ritorno è una codifica e una decodifica, e vale la pena vedere i due lati insieme, perché un bug di archiviazione è quasi sempre un disaccordo di codifica di caratteri tra i due:

const state = { theme: 'dark', draft: 'hello' };
const packed = new TextEncoder().encode(JSON.stringify(state));
localStorage.setItem('app-state', new Uint8Array(packed).toBase64());
const raw = atob(localStorage.getItem('app-state'));
const bytes = Uint8Array.from(raw, (c) => c.codePointAt(0));
const state = JSON.parse(new TextDecoder().decode(bytes));

Fai comunque i conti bene: l'origine ottiene circa 5 megabyte di localStorage, la tua stringa salvata è il 33 percento più grassa dei dati, e mentre la pagina è aperta la stringa vive anche in memoria come UTF-16 - la sua lunghezza raddoppiata di nuovo. Un asset da 3 megabyte sono 4 megabyte di archiviazione e 8 megabyte di memoria, ed è così che una "piccola" funzionalità diventa un errore di quota. Il secondo canale è l'URL stesso: link di condivisione, deep link e stato OAuth vogliono tutti dati strutturati in un luogo che sopravviva al copia-incolla. La ricetta è stato compatto, JSON, poi base64url senza riempimento, in modo che il valore non abbia bisogno di alcuna codifica percentuale - e tieni l'URL intero sotto un paio di migliaia di caratteri, perché è lì che i client più vecchi, i proxy e gli strumenti di logging iniziano a mettersi nervosi.

Email e MIME

Il Base64 è più vecchio del web, e il suo terreno di casa è l'email. Gli allegati MIME con Content-Transfer-Encoding: base64 sono il modo in cui un file binario viaggia dentro un protocollo testuale, e la convenzione nata dal vecchio limite di 76 caratteri per riga del formato di messaggio merita di essere conosciuta: avvolgi il corpo codificato a 76 caratteri per riga. Il browser non può inviare SMTP, ma fa due lavori da email - costruire corpi MIME che un relay di backend invierà, e mostrare gli allegati dei messaggi che riceve - e entrambi toccano la codifica. L'avvolgimento in sé è una funzione di due righe, e l'ordine delle operazioni conta: prima codifica, poi avvolgi, perché btoa non lancerà un'eccezione per un cambio di riga nell'input - codificherà l'interruzione come byte di carico utile, e i tuoi cambi di riga finiranno dentro l'output:

function wrapForMime (base64, width) {
  const w = width || 76;
  return base64.match(new RegExp('.{1,' + w + '}', 'g')).join('\r\n');
}

Il lato ricevente è quello gratuito: atob salta gli spazi bianchi ASCII come parte del suo comportamento standard, quindi un corpo MIME avvolto si decodifica esattamente come è arrivato, a capo inclusi, senza un passo di apertura. Se stai costruendo un client webmail o un selettore di allegati, quell'unica asimmetria - il codificatore deve produrre righe pulite, il decodificatore non se ne cura - è tutta la storia MIME in una frase.

Quando vale la pena una libreria

Con gli strumenti nativi qui sopra, una libreria è raramente necessaria, e la guida onesta è: parti dalla piattaforma, e aggiungi un pacchetto solo quando un requisito reale punta su uno. Le tre che compaiono davvero nei codici:

js-base64 (npm install js-base64) è quella general-purpose: un piccolo trascodificatore JavaScript puro che tratta le stringhe UTF-8 come cittadini di prima classe - Base64.encode su una stringa CJK balla la danza UTF-8 al posto tuo - e, utile tanto per la decodifica quanto per la codifica, accetta entrambi gli alfabeti in decode e include un controllo isValid. È la risposta giusta quando miri a browser dove le API 2025 mancano e vuoi un import solo a coprire stringhe e byte:

import { Base64 } from 'js-base64';
const encoded = Base64.encode('小飼弾'); // "5bCP6aO85by+" - UTF-8 gestito per te
const decoded = Base64.decode('5bCP6aO85by-'); // legge sia lo standard sia quello sicuro per URL
const valid = Base64.isValid(encoded); // true

base64-js è quella focalizzata sui byte: fromByteArray e toByteArray su Uint8Array, nessuna dipendenza, il cavallo da lavoro del vecchio ecosistema browserify e ancora una buona scelta quando il tuo codice vive in typed array e vuoi che la codifica sia una funzione pura dei byte. E se la tua ragione per volere una libreria è "mi piace l'API 2025 ma non posso pretendere browser 2025", la risposta non è un pacchetto Base64 ma un polyfill: core-js (e il preset Babel che lo trascina) implementa Uint8Array.fromBase64 e compagnia, così puoi scrivere il codice in stile nuovo una volta e lasciare che lo shim colmi il vuoto sui motori più vecchi. Scegli per vincolo - browser vecchi, comodità di stringa o purezza di byte - non per abitudine.

Gli inciampi che costano ore ai sviluppatori

  • Chiamare btoa su una stringa con un carattere sopra il punto di codice 255. Lancia, non corrompe, e si ferma al primo colpevole. La correzione è sempre la stessa: prima TextEncoder, poi il ponte.
  • La falesia di fromCharCode.apply sugli array grandi. Un milione di argomenti è un RangeError in entrambi i motori principali. Fai a blocchi il ponte, o passa a toBase64.
  • Dimenticare la tassa dimensionale dove fa più male: l'archiviazione. Un file in localStorage è il 33 percento più grande del file, e la quota è per origine, condivisa con tutto il resto che la tua app salva.
  • Il Base64 standard che incontra una stringa di query. Il + arriva come spazio, lo / spezza il percorso, e i bug report dicono "l'API è instabile". Output sicuro per URL, nessun riempimento, e l'intero genere di bug sparisce.
  • Riempimento incoerente tra i servizi. Una passerella tiene gli =, un'altra li rimuove, una terza li rimette. Il ricevente deve essere pronto per entrambe le forme, e il contratto dovrebbe dire quale è quella canonica.
  • Trattare il Base64 come un lucchetto. È un formato di serializzazione, a una chiamata di funzione dal testo semplice, e "codificato con Base64" in una revisione di sicurezza è un riscontro, non un controllo.
  • Stringhe binarie come modello di memoria. Un megabyte decodificato o codificato viaggia in UTF-16 a due megabyte; un Uint8Array lo contiene a uno. Per carichi utili grandi, tieni i byte in typed array da capo a fine.
  • Doppia codifica. Un valore che era già Base64 viene codificato di nuovo, e il consumatore decodifica una volta e ottiene una stringa di lettere invece di dati. In caso di dubbio, controlla prima di avvolgere - una stringa già nell'alfabeto con riempimento valido è un cattivo segno.
  • Fidarsi del carico utile di un JWT perché si decodifica pulito. La decodificabilità non è autenticità. Verifica la firma con la chiave giusta e l'algoritmo giusto prima di leggere una singola dichiarazione.

Prestazioni: quanto costa un milione di byte

Il Base64 nel browser è economico dove prima era costoso, e il budget ora ha tre righe di spesa invece di una. CPU: su un Firefox recente, Uint8Array.toBase64 codifica dieci megabyte in circa cinque millisecondi, mentre il ponte btoa a blocchi richiede circa quindici volte di più - non perché btoa sia lento, ma perché il ponte costruisce una stringa intermedia gigante lungo la strada. Se il tuo budget di codifica è in millisecondi, usa il metodo nativo; se stai codificando un oggetto di configurazione da 2 kilobyte, entrambi sono sotto la soglia della percezione. Banda: questa è la tassa permanente - ogni byte che codifichi costa 1,33 byte in transito, più l'inquadratura che il trasporto aggiunge. Misura il trasferimento prima di "ottimizzare" la codifica. Memoria: la stringa codificata è l'allocazione transitoria più grande che farai, e per un file da 5 megabyte è una stringa da 6,7 megabyte, o circa 13,4 megabyte di memoria UTF-16 mentre la pagina la tiene. Le conseguenze pratiche escono dall'aritmetica: spezza le codifiche grandi in modo che nessuna stringa singola diventi enorme, libera i byte intermedi non appena la stringa esiste, preferisci URL di oggetto e multipart quando i byte non dovevano mai essere stampabili, e sposta il lavoro multi-megabyte in un Web Worker se il thread principale deve continuare a scorrere senza intoppi. Il formato ha quasi quattro decenni; la piattaforma ha finalmente raggiunto il passo.

Come i browser hanno imparato a codificare

Il codificatore ha una storia, e spiega i relitti che erediterai. btoa - "da binario ad ASCII", il nome è letterale, e atob è solo le stesse parole al contrario - è stato scritto nella specifica HTML nel 2011, ricavato a ritroso dai browser che già lo distribuivano: Firefox dal 2004, Safari 3, Chrome 4. Internet Explorer, fedelmente a se stesso, ha saltato entrambe le funzioni fino alla versione 10 nel 2012, e quella singola assenza è il motivo per cui un decennio di JavaScript è pieno di tabelle Base64 fatte a mano e di un incantesimo particolare per l'Unicode: btoa(unescape(encodeURIComponent(str))). Funzionava - encodeURIComponent produce UTF-8 in percent-encoding, e unescape lo trasformava in una stringa di byte - ma era costruito su unescape(), quello della coppia che il linguaggio aveva deprecato, e ha sopravvissuto nel codice dei browser per anni per pura inerzia. La correzione di principio è arrivata con lo standard Encoding: TextEncoder e TextDecoder, in Firefox 18 (2013), Chrome 38 (2014), Safari 10.1 (2017), e in nessuna versione di IE - un'altra lacuna di IE, un altro decennio di soluzioni alternative. Node.js racconta la metà server della storia: aveva Buffer con Base64 fin dal primo giorno, ma atob e btoa come globali solo dalla versione 16 nel 2021, con due piccoli shim npm a portare il peso prima di allora. E poi, tra la fine del 2024 e il 2025, il linguaggio stesso ha distribuito il Base64 - Uint8Array.toBase64 e compagni in Firefox 133 (novembre 2024), Safari 18.2 (dicembre 2024), Chrome 140 (settembre 2025) e Node 25 (ottobre 2025) - e la funzionalità è stata marchiata Baseline 2025 - lo stesso set di funzionalità che la piattaforma aveva approssimato con funzioni ausiliarie per vent'anni, ora standard. Le curiosità divertenti alla fine dell'articolo parlano per lo più di quanto tempo ci ha messo ogni pezzo ad arrivare.

Lo sapevi?

  • I nomi delle funzioni sono una frase: btoa è "da binario ad ASCII" e atob è "da ASCII a binario". La direzione è nel nome, ed è per questo che la coppia si documenta da sola dai primi anni 2000.
  • La stringa più codificata nella storia del calcolo è probabilmente "hello": btoa('hello') è aGVsbG8=, l'output di ogni tutorial, suite di test e lavagna bianca di colloquio sul pianeta.
  • Ogni stringa Base64 valida ha una lunghezza che è un multiplo di quattro, riempimento incluso. I caratteri = sono un'impronta digitale: uno significa che l'ultimo gruppo conteneva due byte, due significano che ne conteneva uno.
  • L'avvolgimento di riga a 76 caratteri in MIME e nella maggior parte degli strumenti a riga di comando è un'eredità dell'era delle email, quando la lunghezza della riga del formato di messaggio fissava il limite. Il numero ha sopravvissuto a tre decenni di tutto più veloce.
  • "Data URI" è un nome in pensione. Il WHATWG l'ha rinominato "data URL" durante la grande armonizzazione da URI a URL, ed è per questo che specifiche, post di blog e nomi di pacchetto lo scrivono in modi diversi nello stesso paragrafo.
  • btoa('') restituisce '': un input vuoto produce un output vuoto, nessun riempimento, nessun caso speciale - l'unica stringa Base64 a zero caratteri (la sua lunghezza, 0, è comunque un multiplo di quattro).
  • Una canvas può trasformare una foto in un data URL con toDataURL - una capacità che esiste da IE 9, Firefox 2 e Safari 4, precedente alla maggior parte della piattaforma web che consideriamo "moderna" - e può farla viaggiare avanti e ritorno con un tag <img> e un FileReader.
  • La negoziazione WebSocket codifica in Base64 SHA-1(key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11), e la GUID è una costante fissa nell'RFC scelta apposta in modo che nessun server HTTP semplice potesse mai completare la negoziazione per caso.

E da qui, dove andare

Tutto il mestiere della codifica nel browser sta in una pagina: btoa per i casi semplici, a byte singolo, per cui è nato; TextEncoder più il ponte a blocchi per testo vero e file su qualsiasi browser; Uint8Array.toBase64 con le sue opzioni di alfabeto e riempimento per il percorso moderno e diretto; e la variante sicura per URL, con o senza riempimento, per tutto ciò che vivrà in un URL. Il resto è giudizio: conosci la tassa del 33 percento prima di spenderla, tieni i byte in typed array finché sono grandi, prima codifica poi avvolgi, e non chiamare mai un formato di serializzazione un lucchetto. Quando il canale può portare byte grezzi, prendi i byte - il Base64 è per le strade che ammettono solo testo stampabile, e ora sai esattamente come pagare il pedaggio.

L'altra metà del viaggio - ricevere una di queste stringhe e tirare fuori di nuovo i byte, il testo e il significato - è coperta in dettaglio nella guida gemella alla decodifica Base64 in JavaScript, collegata qui sotto.

Ultimo aggiornamento: 2026-09-08

Articolo correlato: Decodifica Base64 in JavaScript/Browser: una guida completa