Приходится иметь дело с форматом Base64? Тогда этот сайт идеально вам подойдет! Воспользуйтесь нашим невероятно удобным онлайн-инструментом для кодирования или декодирования ваших данных.

Кодирование Base64 в JavaScript/браузере: полное руководство

У вас есть что-то, что должно отправиться в путь, а дорога широка только для чистого ASCII. Это может быть изображение, которому место внутри JSON-ответа, конфигурационный объект, который обязан поехать в URL, токен, у которого три сегмента из точек и букв, файл, который API требует прислать Base64-строкой внутри JSON-тела. Base64 - платный пост именно для такой ситуации, и домашняя страница этого сайта уже прошла по формату - четыре печатаемых символа за каждые три байта, со знаками =, чтобы дописать группу, - поэтому вот одно число, которое стоит держать в голове, пока вы читаете: кодирование - это растущее направление. Каждые три сданных байта возвращаются четырьмя символами, налог на размер примерно 33 процента, собираемый полосой пропускания, хранилищем и памятью. Используйте Base64, когда канал требует печатаемый текст, и точно знайте, что стоит вам этот налог.

Утешительная новость в том, что браузер всегда умел делать эту работу без единого пакета. btoa() поставляется с начала 2000-х, TextEncoder десять лет назад превращал ваш настоящий Unicode-текст в честные байты, а в волне Baseline 2025 платформа наконец добавила Uint8Array.toBase64(), который кодирует массивы байтов напрямую, с опцией для URL-безопасного алфавита. Эта статья - карта решений: какой инструмент для какой работы, где прячутся острые края (все они ведут к одной и той же границе) и конкретные рецепты для мест, где от вас действительно запросят произвести Base64.

Выбор кодировщика

Единого «того самого» кодировщика больше нет, и именно замах на неподходящий рождает классические баги. Таблица ниже - всё дерево решений:

Ситуация Берите
Простой ASCII-текст, разовое значение btoa(text)
Настоящий текст с акцентами, эмодзи, CJK new TextEncoder().encode(text), затем btoa или toBase64
Байты уже в Uint8Array bytes.toBase64() в браузерах 2025 года и новее, чанкованный мост btoa - везде остальном
URL, JWT, имена файлов toBase64({ alphabet: 'base64url', omitPadding: true })
Старые браузеры или общий код js-base64 либо классический рецепт TextEncoder + btoa

Паттерн под таблицей: btoa() читает только однобайтовые символы, поэтому всё, что не ASCII, должно сначала стать массивом байтов, и именно этот массив байтов - то, вокруг чего строились современные API. Держите в голове «текст становится байтами, байты становятся Base64», и каждый рецепт в этой статье - те же два шага с разными именами.

btoa и граница Latin1

btoa(stringToEncode) - бинарная строка в ASCII-строку - это оригинальный кодировщик, доступный в каждом браузере, который что-то значит (Chrome 4, Firefox 1, Safari 3, IE 10 и выше, все worker-области, и Node начиная с версии 16). У его договора одна оговорка, и именно на ней всё ломается: каждый символ на входе должен иметь код от 0 до 255. Функция читает коды, а не UTF-8 байты, поэтому «é» (код 233) проскакивает мимо, а «你» (код 20320) бросает DOMException с именем InvalidCharacterError, прежде чем закодирован хотя бы один символ. Граница - это не «ASCII», это не «Unicode», это ровно 256, и она включает управляющие символы внизу - кодирование NUL-байта законно и осмысленно, и это одна из причин, по которой функция вообще существует.

Полное поведение, построчно:

Вход Результат
"Hello, World!" "SGVsbG8sIFdvcmxkIQ==" - классический случай
"" (пустая строка) "" - ничего не вошло, ничего не вышло
"\u0000" (NUL) "AA==" - управляющие символы - граждане первого сорта
"a\u00e9z" (é, код 233) "Yel6" - проходит весь диапазон Latin1
"\u0100" (код 256) бросает InvalidCharacterError - один шаг за границей
"h\u4f60" (你, код 20320) бросает InvalidCharacterError - и каждый эмодзи тоже, потому что все они далеко выше 255

Две практические заметки. Сообщение об ошибке различается в движках - Firefox говорит «String contains an invalid character», Chrome говорит, что строка «contains characters outside of the Latin1 range», - поэтому в защитном коде ловите по имени исключения. И сбой происходит на первом провинившемся символе, а не в конце: btoa не кодирует половину строки и не извиняется. Когда вам на самом деле нужно Latin1-поведение (кодирование байтовой строки, которую нарочно собрали из кодов 0-255), функция делает ровно то, что вы просили, и таблица выше - весь её характер.

Мост байтов

И вопрос становится таким: как реальные данные - UTF-8 байты вашего текста, содержимое файла, вывод canvas - попадают на вход btoa()? Ответ - «мост байтов»: JavaScript-строка, в которой каждый символ хранит значение одного байта, тот же трюк, что производят декодеры и который btoa понимает нативно. Наивная версия - цикл:

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

Верно, но конкатенация строк в цикле медленна для больших файлов, а популярное сокращение - String.fromCharCode.apply(null, bytes), которое скармливает весь массив аргументами в одном вызове, - упирается в жёсткий обрыв. У вызовов функций есть лимит на число аргументов, и он достигается задолго до вашего первого мегабайта:

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

Фикс, который спас больше функций загрузки файлов, чем любое другое единичное изменение, - пересекать мост кусками, по несколько тысяч символов за раз, и склеить результаты:

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(''));
}

Каждый кусок достаточно мал для безопасного apply, subarray даёт представление без копирования, а склейка порождает точно ту же бинарную строку, которую дал бы цикл. Теперь текстовая сторона медали. Для любого настоящего текста TextEncoder - UTF-8-кодировщик платформы, доступный в Firefox 18, Chrome 38, Safari 10.1 и везде с тех пор, - превращает вашу строку в честные байты, прежде чем мост берётся за работу:

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

Такой вывод - то, чем «hello 你好» является на самом деле на проводе: шесть ASCII-байтов плюс шесть UTF-8-байтов за два китайских иероглифа, все в одной и той же печатаемой маске. Если ваш текст не UTF-8 - а в вебе он обычно именно UTF-8, - сначала нужна другая кодировка, а значит, кодировать его где-то, где говорят на ней, обычно на сервере. TextEncoder нарочно отказывается догадываться, и он прав.

Шорткат 2025 года: Uint8Array.toBase64

Если у вас уже есть Uint8Array, мост - это крюк, потому что новая фича ECMAScript (ES2026) кодирует массив напрямую: bytes.toBase64(options). Она прибыла в Chrome 140, Edge 140, Firefox 133, Safari 18.2, Node 25 и Deno 2.5 - та же волна Baseline 2025, что и у её декодирующей сестры, - и принимает две опции, которые превращают её в самый многогранный кодировщик платформы. Первая - alphabet: "base64" (по умолчанию) или "base64url". Вторая - omitPadding: выставьте true, и хвостовые символы = отбрасываются, а это именно та форма, которую хочет большинство URL-дружелюбных потребителей. Передадите что-нибудь другое в опциях - получите TypeError, что есть вежливость API по поводу вашей опечатки:

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="

Эти два байта выбраны максимально невоспитанно по отношению к алфавиту: в стандартном режиме они дают + и /, так что последняя строка показывает ровно, что меняется при переключении на base64url. Производительность - тихий бонус: на свежем Firefox кодирование десяти мегабайт занимает около пяти миллисекунд с toBase64, тогда как путь через строковый мост выше занимает примерно в пятнадцать раз больше, потому что по ходу строит гигантскую промежуточную строку. В старых браузерах мост остаётся вполне пригодным для всего, что меньше нескольких мегабайт, - а чанкованная версия выше - именно та, что вам нужна, по причинам из предыдущего раздела.

URL-безопасный вывод

У Base64 есть отдельный вариант для мест, где +, / и = устраивают повреждения, и он заслуживает собственного раздела, потому что столько сломанного кода - это просто стандартный Base64, которому на пути попался URL. В строке запроса + - это пробел; в пути / - разделитель; а = в некоторых позициях просит процентного кодирования. Безопасный для URL и имён файлов алфавит из раздела 5 RFC 4648 - base64url - меняет эти два символа на - и _, а раз длина данных на приёмной стороне обычно известна, он разрешает отбросить заполнитель целиком. Вывод проходит через URLSearchParams, сегменты пути, фрагменты и имена файлов без единого процентного знака.

С API 2025 года это один объект опций:

const params = new URLSearchParams();
params.set('payload', bytes.toBase64({ alphabet: 'base64url', omitPadding: true }));
console.log(params.toString()); // «payload=-_8» - без процентного кодирования вообще

В старых браузерах преобразуйте после кодирования через btoa. Два replace и trim делают всю работу:

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

Три правила держат канал чистым. Выберите один алфавит на канал и держитесь его - значение, где смешаны + и -, не принадлежит ни одной семье, и ни один декодер не догадается, что вы имели в виду. Заполнитель - это договор, а не рекомендация: если вы его опускаете, получатель должен быть готов к значению без заполнителя, а если оставляете, получатель не должен спотыкаться о него (браузеры снисходительны, некоторые JSON-схемы - нет). И помните, что замена обратима и без потерь - - и _ отображаются на те же 62-ю и 63-ю позиции алфавита, которые занимают + и /, так что при выборе дружелюбной пары ничего не теряется.

Помогаем изображениям путешествовать: data URL

Старейшее и самое видимое применение Base64 в браузере - это data URL: data:, необязательный медиатип, необязательный флаг ;base64, запятая, а потом пелод. Текстовые нагрузки процентно кодируются; бинарные - изображения, шрифты, аудио - в Base64, и браузер отрисовывает их с нулём HTTP-запросов. Для файла изображения, который пользователь только что выбрал, FileReader делает кодирование за вас и отдаёт готовый URL:

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

Результат - готовое src, значение, которое можно сохранить в localStorage или отправить в JSON-теле. Если изображение лежит не в файле, а на canvas - скриншот, обработанное фото, сгенерированный график, - canvas.toDataURL() делает эту работу с самых ранних браузерных релизов, и он даже позволяет выбрать формат и, для форматов с потерями, качество:

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);

Три ловушки, вокруг которых стоит планировать. Первая - правило запачканного canvas: если вы нарисовали кросс-доменное изображение на canvas без разрешения CORS, любая попытка прочитать пиксели обратно - включая toDataURL, - бросает SecurityError. Фикс - загрузить изображение с crossOrigin = 'anonymous' и убедиться, что сервер шлёт правильные заголовки. Вторая - аргумент качества игнорируется для PNG и имеет смысл только для JPEG (и WebP), - частый источник вопроса «почему мой PNG больше». Третья, и самая большая: пелод примерно на 33 процента больше файла, и он сидит в странице строкой. Для изображений, которые никогда не выходят из браузера, есть бесплатная альтернатива - object URL, который оборачивает Blob, вообще его не кодируя:

const objectUrl = URL.createObjectURL(blob);
imageElement.src = objectUrl;
URL.revokeObjectURL(objectUrl); // когда он вам уже не нужен

Разделение труда, которое из этого выходит: object URL для всего, что остаётся на странице, data URL для всего, что нужно скопировать, сохранить или отправить текстом. Оба - граждане первого класса; они просто решают разные задачи.

Создание и подпись JWT

Если вы генерируете токены в браузере - для self-hosted потока аутентификации, демо или serverless фронтенда, - компактный формат JWS - это три base64url-сегмента: заголовок, пелод, подпись, заполнителя нигде. Подпись берёт на себя Web Crypto API; кодирование - ровно тот URL-безопасный вывод из двух разделов назад:

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;

Две детали важнее всей сантехники. Подпись покрывает ровно header + '.' + payload - сырые сегменты, а не JSON, - поэтому любое изменение любой из частей инвалидирует токен, и в этом весь смысл. А crypto.subtle.sign возвращает сырой ArrayBuffer, отсюда однострочная обёртка в Uint8Array перед сегментным кодировщиком. Для токенов на базе RSA поток идентичен с RS256 и парой ключей, и если вы экспортируете публичный ключ как JWK (crypto.subtle.exportKey('jwk', key)), числовые члены - n, e и для приватных ключей d, p, q, - автоматически выходят base64url без заполнителя. Оговорки по безопасности те же, что и для любого токена: заголовок alg: "none" - это просьба пропустить проверку, временные заявления (exp, nbf) должны применяться, а сервер, принимающий и HMAC, и RSA для одной и той же аудитории, открывает классическую дверь путаницы ключей. Кодируйте правильно, подписывайте правильно, проверяйте на стороне приёма.

Заголовки аутентификации

Самая простая схема аутентификации в вебе также наиболее поучительна о том, что такое Base64 и чего он не является. HTTP Basic отправляет Authorization: Basic с последующей Base64-кодировкой username:password - один вызов, мост байтов не нужен, потому что имена пользователей и пароли (надеюсь) - обычный текст:

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

И вот урок, который умещается в одну строку: Base64 - это не шифрование. Заголовок выше - в одном вызове atob от alice:secret123 - и для злоумышленника, и для всех, кто читает логи, - поэтому Basic-аутентификация приемлема только поверх HTTPS, где транспорт - настоящая защита, а Base64 - лишь оформление. Для всего, что живёт дольше одного запроса, предпочитайте токенные схемы: Bearer-токен - тоже один заголовок, но это случайное значение, чей секрет вообще не нужно носить в заголовке, и его можно отозвать. Выбор кодирования между ними тривиален - оба это btoa или обычный текст, - а вот выбор в безопасности нет, и его стоит сделать осознанно.

Файлы на входе, текст на выходе

Загрузки - это место, где 33-процентный налог называют уже в реальных деньгах, потому что файл обычно - самая большая вещь на странице. Есть две дороги, и первая - та, которую стоит брать по умолчанию: multipart-формы данных. FormData несёт файл сырыми байтами в стандартном теле, браузер делает фрейминг, и Base64 здесь нет нигде - ни налога на размер, ни промежуточной строки, и байты стекаются на сервер по мере чтения:

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

Вторая дорога - для API, которые настоятельно требуют JSON-тело с файлом в виде строки - некоторые serverless-функции, некоторые мобильные бэкенды, некоторые легаси-сервисы. Там кодирование - одна строка на файл, и цена ровно та, что обещает налог: файл в 5 мегабайт становится строкой в 6.7 мегабайта, которая затем сериализуется в JSON, который затем отправляется. Для фото - нормально, для видео - больно:

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
});

Для больших файлов на той дороге не стройте одну гигантскую строку в одном вызове - стройте слайсами, где каждый слайс - кратное трём число байтов. Именно это выравнивание делает трюк законным: кратное трём число байтов кодируется в чистое кратное четырём число символов без заполнителя, так что независимо закодированные слайсы склеиваются ровно в кодирование всего файла, и заполнитель несёт только последний слайс:

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('');
}

Идея того же выравнивания объясняет, почему никогда не стоит резать Base64-строку в произвольной точке и ожидать, что куски декодируются сами по себе - трёхбайтовая группа - это атом, и разрез посередине оставляет висеть фрагмент. Скачивания - зеркальное отражение: сгенерированный маленький файл может уйти через data URL в ссылке для скачивания, но для всего существенного Blob плюс object URL - здоровый путь, потому что браузеру в принципе не приходится нести всю пелод строкой.

Сохранение и раздача состояния

Ещё два чисто текстовых канала, где Base64 делает реальную работу. Первый - хранилище: localStorage и sessionStorage держат строки, поэтому структурированные или бинарные данные кодируются прежде, чем попасть туда. Круговое путешествие - одно кодирование и одно декодирование, и обе стороны стоит посмотреть вместе, потому что баг хранилища почти всегда - несовпадение кодировок между ними:

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));

Но посчитайте бюджет как следует: происхождение получает примерно 5 мегабайт localStorage, ваша сохранённая строка на 33 процентов жирнее данных, и пока страница открыта, строка ещё живёт в памяти как UTF-16 - снова вдвое больше по длине. Ассет в 3 мегабайта - это 4 мегабайта хранилища и 8 мегабайт памяти, и так «маленькая» фича превращается в ошибку квоты. Второй канал - сам URL: ссылки для раздачи, глубокие ссылки и OAuth-состояние хотят структурированные данные там, где они переживут копирование. Рецепт - компактное состояние, JSON, затем base64url без заполнителя, так что значение не нуждается ни в каком процентном кодировании, - и держите весь URL меньше пары тысяч символов, потому что именно там старые клиенты, прокси и инструменты логирования начинают нервничать.

Почта и MIME

Base64 старше веба, и его родная стихия - почта. MIME-вложения с Content-Transfer-Encoding: base64 - это то, как бинарный файл едет внутри текстового протокола, и из старого лимита в 76 символов на строку в формате сообщений следует традиция, которую стоит знать: заворачивайте закодированное тело по 76 символов в строку. Браузер не умеет слать SMTP, но он делает две почтовые работы - собирает MIME-тела, которые отправит бэкенд-релей, и отображает вложения входящих сообщений, - и обе касаются кодирования. Само заворачивание - функция из двух строк, и порядок операций важен: сначала кодируем, потом заворачиваем, потому что btoa не бросит исключение на переводе строки во входе - он закодирует перенос как байт пелода, и ваши переводы строк окажутся внутри вывода:

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

Приёмная сторона - бесплатная: atob пропускает ASCII-пробельные символы как часть стандартного поведения, так что завёрнутое MIME-тело декодируется ровно так, как пришло, переводами строк и всем остальным, без шага разворачивания. Если вы строите веб-почтовый клиент или выборщик вложений, то эта единственная асимметрия - кодировщик обязан производить чистые строки, декодер всё равно, - и есть вся MIME-история в одном предложении.

Когда пора браться за библиотеку

С нативными инструментами выше библиотека редко нужна, и честный совет такой: по умолчанию берите платформу, а пакет добавляйте только когда реальное требование указывает на один. Три, которые действительно встречаются в кодовых базах:

js-base64 (npm install js-base64) - универсальная: маленький чисто-JavaScript транскодер, который считает UTF-8 строки гражданами первого класса - Base64.encode на CJK-строке делает UTF-8 танец за вас, - и, что полезно для декодирования не меньше, чем для кодирования, принимает оба алфавита в decode и поставляет проверку isValid. Это правильный ответ, когда вы целитесь в браузеры, где API 2025 года отсутствуют, и хотите, чтобы один импорт покрыл и строки, и байты:

import { Base64 } from 'js-base64';
const encoded = Base64.encode('小飼弾'); // «5bCP6aO85by+» - UTF-8 обрабатывается за вас
const decoded = Base64.decode('5bCP6aO85by-'); // читает и стандартный, и URL-safe вариант
const valid = Base64.isValid(encoded); // true

base64-js - байто-ориентированная: fromByteArray и toByteArray на Uint8Array, без зависимостей, рабочая лошадка старой экосистемы browserify и по-прежнему неплохой выбор, когда ваш код живёт в типизированных массивах и вы хотите, чтобы кодирование было чистой функцией байтов. А если ваша причина хотеть библиотеку - «мне нравится API 2025 года, но я не могу требовать браузеры 2025 года», - ответ вообще не в Base64-пакете, а в полифилле: core-js (и Babel-пресет, который втаскивает его) реализует Uint8Array.fromBase64 и друзей, так что вы пишете код в новом стиле один раз и даёте шиму заполнить пробел на старых движках. Выбирайте по ограничению - старые браузеры, удобство со строками или чистота байтов, - а не по привычке.

Ловушки, от которых разработчики теряли часы

  • Вызов btoa на строке с символом выше кода 255. Она бросает исключение, а не калечит, и останавливается на первом нарушителе. Фикс всегда один: сначала TextEncoder, потом мост.
  • Обрыв fromCharCode.apply на больших массивах. Миллион аргументов - это RangeError в обоих главных движках. Чанкуйте мост или переходите на toBase64.
  • Забывание налога на размер там, где больнее всего: в хранилище. Файл в localStorage на 33 процента больше самого файла, а квота - на происхождение, общая со всем остальным, что сохраняет ваше приложение.
  • Стандартный Base64 встречает строку запроса. + приезжает пробелом, / ломает путь, и в баг-репортах пишут «API капризный». URL-безопасный вывод, без заполнителя, и целый жанр багов исчезает.
  • Несогласованный заполнитель между сервисами. Один шлюз держит =, другой срезает, третий добавляет обратно. Получатель должен быть готов к обеим формам, а договор должен говорить, какая из них каноническая.
  • Отношение к Base64 как к замку. Это формат сериализации, один вызов функции от открытого текста, и «закодировано в Base64» в ревью безопасности - это находка, а не мера контроля.
  • Бинарные строки как модель памяти. Декодированный или закодированный мегабайт едет в UTF-16 на двух мегабайтах; Uint8Array держит его на одном. Для больших нагрузок держите байты в типизированных массивах от начала до конца.
  • Двойное кодирование. Значение, которое уже было Base64, кодируют ещё раз, и потребитель декодирует один раз - и вместо данных получает строку из букв. Если сомневаетесь, проверьте до обёртки - строка, которая уже в алфавите и с корректным заполнителем, - это запах.
  • Доверие пелоду JWT потому, что он чисто декодировался. Декодируемость - это не подлинность. Проверьте подпись правильным ключом и правильным алгоритмом, прежде чем читать хоть одно заявление.

Производительность: сколько стоит миллион байтов

Base64 в браузере дёшев там, где раньше был дорог, и в бюджете теперь три строки вместо одной. CPU: на свежем Firefox Uint8Array.toBase64 кодирует десять мегабайт за примерно пять миллисекунд, тогда как чанкованный мост btoa занимает примерно в пятнадцать раз больше - не потому что btoa медленный, а потому что мост по дороге строит гигантскую промежуточную строку. Если ваш бюджет кодирования - в миллисекундах, используйте нативный метод; если вы кодируете конфигурационный объект в 2 килобайта, оба варианта ниже порога восприятия. Полоса пропускания: это постоянный налог - каждый закодированный байт стоит 1.33 байта на проводе, плюс всё, что добавляет фрейминг транспорта. Измеряйте передачу, прежде чем «оптимизировать» кодирование. Память: закодированная строка - самое большое временное выделение, которое вы сделаете, и для файла в 5 мегабайт это строка в 6.7 мегабайта, или около 13.4 мегабайт UTF-16 памяти, пока страница её держит. Практические последствия вытекают из арифметики: режьте большие кодирования, чтобы ни одна строка не стала гигантской, отпускайте промежуточные байты, как только строка существует, предпочитайте object URL и multipart, когда байтам и так не нужно было быть печатаемыми, и переносите мультимегабайтную работу в Web Worker, если главный поток должен плавно продолжать прокрутку. Формату почти четыре десятилетия; платформа наконец догнала его.

Как браузеры научились кодировать

У кодировщика есть история, и она объясняет реликвии, которые вы унаследуете. btoa - «бинарное в ASCII», название буквальное, а atob - те же слова в обратном порядке, - записали в HTML-спецификацию в 2011 году, вывернув наизнанку браузеры, которые уже поставляли её: Firefox с 2004 года, Safari 3, Chrome 4. Internet Explorer, что характерно, пропустил обе функции до версии 10 в 2012 году, и этого единственного отсутствия хватило, чтобы декада JavaScript наполнилась самописными таблицами Base64 и одним особенным заклинанием для Unicode: btoa(unescape(encodeURIComponent(str))). Оно работало - encodeURIComponent производит процентно-экранированный UTF-8, а unescape превращал это в байтовую строку, - но оно стояло на unescape(), той из пары, которую язык объявил устаревшей, и оно продержалось в браузерном коде годы чистой инерцией. Принципиальный фикс пришёл со стандартом Encoding: TextEncoder и TextDecoder, в Firefox 18 (2013), Chrome 38 (2014), Safari 10.1 (2017) и ни в одной версии IE, - ещё одна IE-брешь, ещё одна декада обходных путей. Node.js рассказывает серверную половину истории: у него с первого дня был Buffer с Base64, но atob и btoa как глобалы - только с версии 16 в 2021 году, до этого нагрузку держали два мелких npm-шима. А потом, по ходу конца 2024-го и 2025-го, сам язык поставил Base64 - Uint8Array.toBase64 и друзей в Firefox 133 (ноябрь 2024), Safari 18.2 (декабрь 2024), Chrome 140 (сентябрь 2025) и Node 25 (октябрь 2025), - и фича была помечена Baseline 2025 - тот самый набор, который платформа двадцать лет приближала вспомогательными функциями, теперь стандартный. Весёлые факты в конце статьи в основном о том, сколько времени на приезд брала каждая деталь.

Знали ли вы?

  • Имена функций - это фраза: btoa - это «бинарное в ASCII», а atob - «ASCII в бинарное». Направление - в самом имени, поэтому эта пара само-документируется с 2000-х.
  • Самая кодируемая строка в истории вычислительной техники - скорее всего «hello»: btoa('hello') даёт aGVsbG8=, вывод каждого туториала, тестового набора и интервью-маркерборда на планете.
  • У каждой корректной Base64-строки длина кратна четырём, заполнитель включён. Символы = - это отпечаток пальцев: один из них значит, что в последней группе было два байта, два - что один.
  • Перенос строк по 76 символов в MIME и в большинстве командно-строчных инструментов - наследие эпохи почты, когда длину строки лимитировал формат сообщений. Число пережило три десятилетия ускорения всего.
  • «Data URI» - вышедшее из употребления название. WHATWG переименовал его в «data URL» во время великой гармонизации URI в URL, поэтому спецификации, блоги и имена пакетов пишут по-разному в одном абзаце.
  • btoa('') возвращает '': пустой вход даёт пустой вывод, без заполнителя, без особой ситуации - единственная Base64-строка с нулём символов (её длина, 0, всё равно кратна четырём).
  • Элемент canvas превратит фото в data URL через toDataURL - возможность, которая существует с IE 9, Firefox 2 и Safari 4, то есть старше большей части веб-платформы, которую мы считаем «современной», - и вернёт его обратно с тегом <img> и FileReader.
  • WebSocket-рукопожатие кодирует SHA-1(key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11) в Base64, и GUID - фиксированная константа в RFC, выбранная ровно так, чтобы ни один обычный HTTP-сервер не смог случайно завершить рукопожатие.

Куда идти дальше

Всё мастерство кодирования в браузере умещается на одной странице: btoa для простых однобайтовых случаев, ради которых он и появился; TextEncoder плюс чанкованный мост для настоящего текста и файлов в любом браузере; Uint8Array.toBase64 с его опциями алфавита и заполнителя для современного прямого пути; и URL-безопасный вариант, с заполнителем или без, для всего, что будет жить в URL. Остальное - суждение: знайте 33-процентный налог до того, как его заплатить, держите байты в типизированных массивах, пока они большие, сначала кодируйте, потом заворачивайте, и никогда не называйте формат сериализации замком. Когда канал может нести сырые байты, берите байты - Base64 для дорог, которые пускают только печатаемый текст, и теперь вы точно знаете, как оплатить проезд.

Другая половина пути - принять одну из таких строк и вытащить из неё обратно байты, текст и смысл - подробно разобрана в сопутствующем руководстве по декодированию Base64 в JavaScript, ссылка ниже.

Последнее обновление: 2026-09-08

Связанная статья: Декодирование Base64 в JavaScript/браузере: полное руководство