JavaScript/Browser में Base64 डिकोडिंग: एक सम्पूर्ण गाइड
यह एक दर्जन अलग-अलग अलाव-पलावों में सामने आता है: Authorization हेडर में छुपा हुआ JWT, JSON रिस्पॉन्स के अंदर image/png ब्लॉब, हैंडशेक लॉग में Sec-WebSocket-Accept वाला मान, MIME में लिपा हुआ ईमेल अटैचमेंट, और वह मान जो आपका बैकएंड क़रार से क्वेरी स्ट्रिंग में ठुँस दिया हो। स्ट्रिंग की शक्ल हमेशा एक जैसी रहती है: अक्षरों और अंकों की लंबी कतार, कभी-कभार + या /, और शायद अंत में एक-दो =। अगर इस साइट का होम पेज आपको Base64 क्या है - हर तीन बाइट की जगह खड़े चार प्रिंटेबल चर, और आख़िरी ग्रुप को पूरा करने के लिए = पैडिंग - यह समझा चुका है, तो यह लेख उस हिस्से के बारे में है जो आप असल में कोड में करते हैं: उन चरों को वापस बाइट्स में बदलना, और बाइट्स को वापस अर्थ में, बस उन्हीं चीज़ों से जो ब्राउज़र पहले से ही साथ लाता है।
शुरू करने से पहले दो तेज़ ज़मीनी नियम। पहला, डिकोडिंग वह दिशा है जिसमें डेटा सिकुड़ता है: हर चार चर जो आप अंदर पढ़ते हैं, तीन बाइट बाहर आते हैं, इसलिए आउटपुट हमेशा इनपुट से कम मेमोरी लेता है। दूसरा, डिकोड की गई Base64 स्ट्रिंग अपने आप टेक्स्ट नहीं हो जाती। यह बाइट्स है, और ये बाइट्स UTF-8 भी हों, Windows-1252 भी, PNG हेडर भी, या क्राइप्टोग्राफ़िक सिग्नेचर भी। Base64 कोड में सबसे आम बग वही है कि आप भूल जाते हैं कि आपके हाथ में उनमें से कौन सा है, इसलिए नीचे के सेक्शन उसी सवाल के इर्द-गिर्द सेट हैं।
डिकोडिंग की तीन परतें
आधुनिक ब्राउज़र आपको तीन नेटिव परतें देते हैं, और अच्छी बात यह है कि किसी पैकेज की कभी ज़रूरत नहीं पड़ती। हर एक थोड़ा-अलग सवाल जवाब देती है, और सही परत चुनने से आपको ज़्यादा-से-ज़्यादा कॉपी-पेस्ट किए Stack Overflow स्निपेट्स से बचना पड़ता है:
| परत | यह क्या खाती है | यह आपको क्या सौंपती है | व्यक्तित्व | उपलब्धता |
|---|---|---|---|---|
atob() |
स्टैंडर्ड Base64 स्ट्रिंग | एक "बाइनरी स्ट्रिंग" (हर चर में एक बाइट) | बहुत बुज़दिल: ASCII व्हिटस्पेस छोड़ देता है, ग़ायब पैडिंग स्वीकार करता है | 2000 के दशक से हर ब्राउज़र, IE 10+, Node 16+ |
TextDecoder |
बाइट्स (Uint8Array) |
पठनीय JavaScript टेक्स्ट | कॉन्फ़िगर करने लायक: कैरेक्टर सेट के लिए लेबल, सख़्ती के लिए fatal फ़्लैग |
Firefox 18, Chrome 38, Safari 10.1 और उसके बाद (IE में कभी नहीं) |
Uint8Array.fromBase64() |
Base64 स्ट्रिंग साथ में ऑप्शन | असली Uint8Array |
सख़्त, डायलों के साथ: वर्णमाला और आख़िरी चंक की संभाल | Baseline 2025: Chrome 140, Firefox 133, Safari 18.2, Node 25 |
पूरे लेख की शक्ल उसी टेबल से निकलती है। atob() वह कर्णभार है जो आपको हर जगह मिलेगा, पुराने कोड में समेत। TextDecoder बाइट्स से शब्दों तक का पुल है। और Uint8Array.fromBase64() वह 2025 का अपग्रेड है जो बीच का कदम बिल्कुल छोड़ देता है, जब आपका इरादा शुरू से ही सिर्फ़ बाइट्स तक था।
atob: तेज़, बुज़दिल, और बहुत पुराना
पूरा करार एक ही पंक्ति में समा जाता है: atob(encodedData)। यह एक Base64-एन्कोडेड स्ट्रिंग लेता है और एक "बाइनरी स्ट्रिंग" लौटाता है: एक साधारण JavaScript स्ट्रिंग जिसमें हर चर ठीक एक डिकोड किया हुआ बाइट रखता है, 0 से 255 तक का कोई कोड पॉइंट। वह रिटर्न टाइप मायने रखता है, क्योंकि यह पठनीय टेक्स्ट से एक ही चीज़ नहीं है (उसके बारे में आगे नीचे)। फ़ंक्शन ख़ुद जितना तेज़ हो सकता है उतना ही तेज़ है, और बहुत पुराना भी है: Chrome 4, Firefox 1, Safari 3, और - यह वह बात है जो सबसे ज़्यादा लोग याद रखते हैं - Internet Explorer सिर्फ़ वर्ज़न 10 से आगे, यही वजह है कि 2012 से पहले लिखा गया कोड हाथ से बनी Base64 टेबल से भरा पड़ा है।
atob() को पसंद करने लायक बनाता है यह कि वह हार मानने से पहले कितना सब्र कर देता है। WHATWG HTML मानक कहता है कि डिकोडिंग से पहले सारा ASCII व्हिटस्पेस नज़रअंदाज़ कर दिया जाए - स्पेस, टैब, लाइन फ़ीड, फ़ॉर्म फ़ीड, कैरिएज रीटर्न - इसलिए MIME-रैप की गई स्ट्रिंग, जिसमें हर 76 चरों पर नई लाइन आती है, आपकी किसी सफ़ाई के बिना ही डिकोड हो जाती है। ग़ायब पैडिंग भी माफ़ हो जाती है। मगर जिस पल भी वह वर्णमाला से बाहर का कोई चर देखता है, या ऐसी लंबाई जो कभी वैध नहीं हो सकती, वह एक DOMException थ्रो कर देता है जिसका नाम InvalidCharacterError है। न चुपचाप कचरा, न आधा-अधुरा रिज़ल्ट।
यह नुक़सान की रिपोर्ट है, पंक्ति-दर-पंक्ति:
| इनपुट | रिज़ल्ट |
|---|---|
"SGVsbG8sIFdvcmxkIQ==" |
"Hello, World!" - पाठ्यपुस्तक का मामला |
"aGVsbG8" (बिना पैडिंग के) |
"hello" - ग़ायब = माफ़ हो जाता है |
"SGVs\nbG8s\nIFdvcmxkIQ==" (रैप की गई लाइनें) |
"Hello, World!" - पहले ASCII व्हिटस्पेस छला जाता है |
"" (ख़ाली स्ट्रिंग) |
"" - ख़ाली इनपुट वैध है और रउंड ट्रिप करता है |
"A" (एक बाकी रह गया चर) |
InvalidCharacterError थ्रो होता है - एक चर कुछ भी एन्कोड नहीं कर सकता |
"Zm9vYmFy!" (बेजगह !) |
InvalidCharacterError थ्रो होता है - वर्णमाला से बाहर |
"ZGFua29nYWk-" (URL-सुरक्षित चर मिली-जुला) |
InvalidCharacterError थ्रो होता है - दोनों वर्णमालाएँ मिलाकर नहीं चली |
"Zm9v====" (ज़्यादा पैडिंग) |
InvalidCharacterError थ्रो होता है - अंत में ज़्यादा से ज़्यादा दो = |
एक अमली नोट: एरर मैसेज ख़ुद इंजन से इंजन अलग होता है (Firefox कहता है "String contains an invalid character", Chrome नॉन-Latin1 इनपुट के लिए कहता है कि स्ट्रिंग में "contains characters outside of the Latin1 range" है, और ग़लत base64 के लिए "is not correctly encoded"), इसलिए एक्सेप्शन के नाम पर कैच करें, मैसेज के टेक्स्ट पर नहीं।
राव बाइट्स से असली टेक्स्ट तक
वह "बाइनरी स्ट्रिंग" रिटर्न टाइप एक ठहराव की हक़दार है, क्योंकि अधिकांश डिकोडिंग उलझनों का स्रोत वही है। JavaScript स्ट्रिंग्स UTF-16 होती हैं, इसलिए atob() आपको ऐसी स्ट्रिंग सौंपता है जिसके चर बाइट वैल्यू हैं, पठनीय ग्लाइफ़ नहीं। अगर आपका पेलोड टेक्स्ट "hello 你好" का UTF-8 एन्कोडिंग था, तो रिज़ल्ट सीधे प्रिंट करने पर आपको मोजिबैक मिलेगा। इलाज है दो-चरणीय डिकोड: Base64 से बाइट्स, फिर बाइट्स से टेक्स्ट।
पहला कदम, Base64-से-बाइट्स वाला। यह छोटा सा हेल्पर क्लासिक रेसिपी है और जेब में रखने लायक, क्योंकि इस लेख के अधिकांश उदाहरणों में यही वह कर्णधार टुकड़ा है:
function base64ToBytes (base64) {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i += 1) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
फिर बाइट्स-से-टेक्स्ट का कदम, TextDecoder के साथ। UTF-8 के लिए (यह डिफ़ॉल्ट है, और JSON, JWT पेलोड, और अधिकांश वेब डेटा के लिए सही चुनाव भी) कॉल सिर्फ़ एक पंक्ति की:
const bytes = base64ToBytes('aGVsbG8g5L2g5aW9');
const text = new TextDecoder('utf-8').decode(bytes);
console.log(text); // "hello 你好"
दो कदम ही क्यों? क्योंकि atob() को इसका पता भी नहीं कि बाइट्स किस कैरेक्टर सेट में बने थे। यह शुद्ध बिट-कन्वर्टर है। TextDecoder वह घटक है जो बाइट्स को किसी कैरेक्टर सेट की नज़र से पढ़ता है, और काम के लिए वह एक लेबल स्वीकार करता है: utf-8, windows-1252, iso-8859-1, utf-16le, और करीब 220 और लेबल। 1990 के दशक की किसी एप्लीकेशन से निकला डेटा आमतौर पर Windows-1252 का होता है, और इसके लिए बस एक कॉन्स्ट्रक्टर आर्गुमेंट काफ़ी है:
const decoder = new TextDecoder('windows-1252');
const text = decoder.decode(bytes); // वही बाइट्स, अलग तफ़सीर
TextDecoder कॉन्स्ट्रक्टर एक fatal फ़्लैग भी लेता है, और जब भी डिकोड किया हुआ टेक्स्ट किसी मायने वाली चीज़ को ख़िलता है, उसे true पर सेट करने का हर हक़ है। डिफ़ॉल्ट में डिकोडर ढीला रहता है: ग़लत बाइट अनुक्रम चुपचाप Unicode रीप्लेसमेंट चर, U+FFFD, से बदल दिए जाते हैं, और आपको कभी बताते भी नहीं। fatal: true के साथ वही नुक़सान छुपने के बजाय TypeError थ्रो करता है:
const strict = new TextDecoder('utf-8', { fatal: true });
try {
strict.decode(corruptedBytes);
} catch (error) {
console.log(error.name); // "TypeError"
}
यह उन स्विचों में से एक है जो डॉक्स में छोटा सा लगता है और प्रोडक्शन में डेटा-इнциडेंट बनकर सामने आता है। अगर आपका इनपुट यूज़र से आया हो या नेटवर्क से, तो सख़्ती से डिकोड करें और एरर को इरादे से हैंडल करें।
URL-सुरक्षित इनपुट के लिए एक दुगली ज़रूरी है
Base64 का एक वैरिएंट अपना अलग सेक्शन पाता है, क्योंकि वह बाहर की दुनिया में हर पल सामने आता है और atob() उसे पढ़ता ही नहीं। यह RFC 4648, सेक्शन 5 की URL और फ़ाइल-नाम सुरक्षित वर्णमाला है, जिसका आम नाम base64url है: वही 64 चर, सिर्फ़ इतना फ़र्क़ कि + और / की जगह - और _ आते हैं, और = पैडिंग अक्सर छोड़ दी जाती है, क्योंकि डेटा की लंबाई इम्प्लीसिट रूप से मालूम है। इस बदलाव के पीछे एक साफ़ वजह है: URL में + का मतलब स्पेस होता है और / पाथ सेगमेंट शुरू करता है, इसलिए स्टैंडर्ड वर्णमाला को चर-दर-चर परसेंट-एन्कोड करना पड़ता। Base64url क्वेरी स्ट्रिंग्स, पाथ सेगमेंट्स, फ़्रैगमेंट्स और फ़ाइल-नामों में बिना किसी खुरदुरेपन के चलता है।
पकड़ यह है कि दोनों वर्णमालाएँ एक-दूसरी की जगह नहीं चलतीं, और atob() सिर्फ़ स्टैंडर्ड वाली बोलता है। इसमें - या _ डालें तो आपको InvalidCharacterError मिलेगा। आपके पास दो साफ़ रास्ते हैं।
पहला रास्ता, जो हर जगह चलता है: atob() कॉल करने से पहले वर्णमाला बदल लें और पैडिंग बहाल कर लें:
function fromUrlBase64 (segment) {
let s = segment.replace(/-/g, '+').replace(/_/g, '/');
const missing = (4 - (s.length % 4)) % 4;
return atob(s + '='.repeat(missing));
}
console.log(fromUrlBase64('aGVsbG8')); // "hello"
(4 - (s.length % 4)) % 4 एक्सप्रेशन ही पूरा चक्र है: यह गिनता है कि उस लंबाई की एक बखूबी पैड की गई स्ट्रिंग को कितने = चरों की ज़रूरत होगी, शून्य से लेकर दो तक।
दूसरा रास्ता, 2025+ ब्राउज़रों में: नया नेटिव डिकोडर वर्णमाला को एक ऑप्शन के तौर पर लेता है, इसलिए स्ट्रिंग की कोई सर्जरी नहीं:
const bytes = Uint8Array.fromBase64('P3-0', { alphabet: 'base64url' });
console.log(Array.from(bytes).join(', ')); // "63, 127, 180"
दो नियम आपको मुसीबत से दूर रखते हैं। एक ही वैल्यू के अंदर कभी वर्णमालाएँ मत मिलाएँ - ऐसे डिकोडर के पास + और - दोनों दिखते हैं तो यह जानने का कोई तरीका नहीं कि वह किस कुल का पता पढ़ रहा है, और स्पेसिफ़िकेशन-सही व्यवहार ही फेल होना है। और वायर के दूसरे सिरे वाले से पैडिंग मौजूद है या नहीं, इस पर तय कर लें: base64url के लिए उसे छोड़ना क़ानूनी है, इसलिए रिसीवर दोनों शक्लों के लिए तैयार होना चाहिए। atob() पहले से ही तैयार है; नीचे दिए नेटिव ऑप्शन आपको इसके लिए एक डायल देते हैं।
2025 की छोटकरी: Uint8Array.fromBase64
अगर आप base64ToBytes हेल्पर पर वापस नज़र डालें, तो आप देखेंगे कि यह दो काम करता है: Base64 डिकोड करना, फिर JavaScript में चरों को एक-दर-एक बाइट ऐरे में कॉपी करना। वह कॉपी लूप ही वह धीमा, बचने लायक हिस्सा है, और नया ECMAScript मीथड ठीक वही हटा देता है। Uint8Array.fromBase64(string, options) एन्कोडेड स्ट्रिंग से सीधे बाइट ऐरे पर जाता है, और यह Chrome 140, Edge 140, Firefox 133, Safari 18.2, Node 25 और Deno 2.5 में शिप होता है - इस तरह की पहली JavaScript प्लेटफ़ॉर्म सुविधा जो उतरी है, ब्राउज़र वेनडर्स के Baseline प्रोग्राम में इसे Baseline Newly available चिह्नित किया गया है।
ऑप्शन ऑब्जेक्ट में दो डायल हैं। पहला alphabet: "base64" (डिफ़ॉल्ट) या "base64url"। दूसरा lastChunkHandling, जो तय करता है कि आख़िरी अधूरे चर ग्रुप के साथ क्या होता है:
| मोड | आख़िरी चंक का नियम |
|---|---|
"loose" (डिफ़ॉल्ट) |
दो या तीन चर, या पैडिंग के साथ चार; बाकी रह गए ओवरफ़्लो बिट्स नज़रअंदाज़ |
"strict" |
बिल्कुल चार चर (पैडिंग सिर्फ़ जहाँ लंबाई माँगती है), और ओवरफ़्लो बिट्स सब शून्य होने चाहिए |
"stop-before-partial" |
सिर्फ़ पूरे चार-चर ग्रुप डिकोड होते हैं; अधूरी पूँछ अछूती रह जाती है |
atob() की तरह, यह मीथड इनपुट में ASCII व्हिटस्पेस को नज़रअंदाज़ करता है, इसलिए रैप की गई लाइनें ठीक हैं। atob() से अलग, वह बाक़ी सब में राय रखता है: चुनी गई वर्णमाला से बाहर का कोई चर, या चुने गए मोड का उल्लंघन करने वाला आख़िरी चंक, SyntaxError थ्रो करता है; और स्ट्रिंग न हो कुछ पास करने पर TypeError थ्रो करता है। यह रहा strict मोड काम पर, जो अपने पैडिंग से वंचित एक चंक को मना करता है:
const ok = Uint8Array.fromBase64('SGVsbG8=', { lastChunkHandling: 'strict' });
try {
Uint8Array.fromBase64('VR', { lastChunkHandling: 'strict' });
} catch (error) {
console.log(error.name); // "SyntaxError"
}
परफ़ॉर्मेंस ही दूसरा कारण है जिससे इसे पसंद करें। लेखक की मशीन के एक ताज़ा Firefox पर, 10 मेगाबाइट के पेलोड को fromBase64 से डिकोड करने में सिंगल-डिजिट मिलीसेकंड लगते हैं, जबकि क्लासिक atob प्लस चर-दर-चर बाइट मैपिंग लगभग बीस गुना ज़्यादा समय लेती है, क्योंकि धीमा हिस्सा JavaScript-स्तरीय लूप है, Base64 की गणित नहीं। अगर आपका डेटा बाइट्स है, तो स्ट्रिंग को बिल्कुल छोड़ दें।
पुराने ब्राउज़रों के लिए स्थिति साफ़ है: उपर दिया base64ToBytes हेल्पर ही रख लें, या अगर आप हर जगह नए स्टाइल का कोड लिखना चाहते हैं तो एक छोटा सा पॉलीफ़िल खींच लें (core-js और es-shims प्रोजेक्ट का es-arraybuffer-base64 पैकेज दोनों fromBase64 के लिए एक-एक पॉलीफ़िल शिप करते हैं)। API स्थिर है - यह अब ECMAScript स्पेसिफ़िकेशन में है - इसलिए इस पर लिखा गया कुछ भी डीप्रेकेट होने वाला नहीं है।
JWT पढ़ना
एप्लीकेशन लॉग्स में सबसे आम "रहस्यमयी स्ट्रिंग" JSON Web Token है: तीन डॉट से अलग किए गए सेगमेंट, header.payload.signature, जहाँ पहले दो base64url-एन्कोडेड JSON ऑब्जेक्ट हैं। एक को डिकोड करना पाँच पंक्तियों का काम है, और यह अब तक की हर चीज़ के लिए बिल्कुल सही वार्म-अप है:
function jwtSegmentToBytes (segment) {
let s = segment.replace(/-/g, '+').replace(/_/g, '/');
s += '='.repeat((4 - (s.length % 4)) % 4);
return base64ToBytes(s);
}
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const [header64, payload64] = token.split('.');
const payload = JSON.parse(new TextDecoder().decode(jwtSegmentToBytes(payload64)));
console.log(payload.name); // "John Doe"
अब वह हिस्सा जो शुरुआती लोग छोड़ देते हैं और प्रोडक्शन सिस्टम मुश्किलों से सीखते हैं: पेलोड के डिकोड होने से उसका सत्यापन नहीं होता। कोई भी अपना मनमौजा पेलोड लेकर JWT लिख सकता है; जो इसे सीक्रेट से बंधाता है वह सिग्नेचर सेगमेंट है। ब्राउज़र में HS256 टोकन सत्यापित करने के लिए Web Crypto API इस्तेमाल होती है, जिसके लिए सिग्नेचर बाइट्स के रूप में चाहिए - यह एक और वजह है कि सेगमेंट-से-बाइट्स वाला हेल्पर अपना ख़र्च कमाता है:
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
encoder.encode('shared-secret'),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
);
const [h, p, sig64] = token.split('.');
const valid = await crypto.subtle.verify(
'HMAC',
key,
jwtSegmentToBytes(sig64),
encoder.encode(h + '.' + p)
);
console.log(valid); // true, सिर्फ़ तब जब सिग्नेचर सीक्रेट से मेल खाए
तीन गड्ढों को नाम लेने की हक़ है। पहला, सत्यापन करने से पहले हेडर की जाँच करें: ऐसा टोकन जो alg: "none" का दावा करता है, वह आपको बिना सिग्नेचर के पेलोड पर भरोसा करने का आग्रह करता है, और बेनाक़ कोड को ठीक उसी बात में फँसाया जा चुका है। दूसरा, टाइम क्लेम्स - exp, nbf, iat - का हक़ रखा जाए, सत्यापन के बाद, पहले नहीं। तीसरा, क्लासिक की-कन्फ़्यूज़न अटैक: ऐसा सर्वर जो RS256 के लिए सेट किया हो पर HS256 को भी स्वीकार करे, वह अटैकर को पब्लिक कुंजी (जो जानबूझकर पब्लिक है) को HMAC सीक्रेट बनाकर टोकन साइन करने दे देता है। शब्दों में: जोर से डिकोड करें, किसी पर भरोसा न करें, सब कुछ सत्यापित करें।
डेटा URLs खोलना
डेटा URL एक पूरी फ़ाइल को URL के अंदर उतार देता है: data:, वैकल्पिक मीडिया टाइप, वैकल्पिक ;base64 फ़्लैग, एक कॉमा, और फिर पेलोड। टेक्स्ट पेलोड्स परसेंट-एन्कोडेड होते हैं, बाइनरी पेलोड्स Base64, और ब्राउज़र उन्हें बिना किसी HTTP रिक्वेस्ट के रेंडर करता है - न कोई फ़ेटच, न सर्वर रउंड ट्रिप, न कैश करने को कुछ। ब्राउज़र हर डेटा URL को एक अलग, अज्ञात ओरिजिन की तरह मानता है, और यही वजह है कि ये छिपाकर डाले गए कंटेंट के लिए पसंदीदा रास्ता बन गए हैं: इफ्रेम में खोला गया data:text/html दस्तावेज़ अपने स्क्रिप्ट चला लेता है, और कड़ाई वाला Content-Security-Policy डेटा URLs को बिल्कुल ब्लॉक कर सकता है। अगर आप यूज़र-कंट्रोल्ड मार्कअप में इनका इस्तेमाल शुरू करें, तो CSP को ध्यान में रखें।
इसे डिकोड करना ज़्यादातर स्ट्रिंग सर्जरी है, फिर वही बाइट्स पाइपलाइन जो पहले का है:
const url = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADgQFY/fWoOgAAAABJRU5ErkJggg==';
const comma = url.indexOf(',');
const meta = url.slice(5, comma); // "image/png;base64"
const bytes = base64ToBytes(url.slice(comma + 1));
const blob = new Blob([bytes], { type: 'image/png' });
const objectUrl = URL.createObjectURL(blob);
meta हिस्सा आपको मीडिया टाइप बताता है (यहाँ image/png, और ;base64 मार्कर पक्का करता है कि पेलोड Base64 है)। पेलोड एक Blob बनते ही सब सामान्य नियम लागू होते हैं: <img> के लिए ऑब्जेक्ट URL, डाउनलोड लिंक, या सर्वर पर POST। डेटा-URL रास्ते की असली कीमत सिर्फ़ साइज़ है - पेलोड मूल फ़ाइल से करीब 33 फ़ीसदी बड़ा रहता है - और URL में बड़ी छवि पेज की स्ट्रिंग सीमाओं पर दबाव डाल सकती है, जो यह भी वोट है कि जब फ़ाइल को ब्राउज़र से बाहर जाने की ज़रूरत ही न हो, तब ऑब्जेक्ट URL ही बेहतर है।
टेक्स्ट के रूप में आई फ़ाइलों की डिकोडिंग
फ़ाइलें ब्राउज़र तक दो रास्तों से पहुँचती हैं। आधुनिक रास्ता राव बाइट्स का है: fetch जिसे आप ArrayBuffer के तौर पर पढ़ते हैं, या पिकर से मिला File जिसे आप file.arrayBuffer() से पढ़ते हैं। अगर आप उसी रास्ते पर हैं, तो मुबारक हो - उसमें Base64 का नामोनिशान तक नहीं है, और आप उसी रास्ते पर रहें, क्योंकि बाइट्स को साथ ले जाने का कोई ख़र्च नहीं, जबकि Base64 इसी ख़ासियत के लिए बैंडविड्थ और मेमोरी का एक-तिहाई अतिरिक्त कर वसूलता है। दूसरा रास्ता तब है जब चैनल सिर्फ़ टेक्स्ट का हो: ऐसा JSON API जो {"attachment": "data:application/pdf;base64,JVBERi..."} लौटाता है, ईमेल अटैचमेंट, कॉन्फ़िग स्ट्रिंग, या डेटाबेस कॉलम में एक वैल्यू। तब Base64 ही प्रोटोकॉल बन जाता है, और आपका काम बस बाइट्स बाहर निकालने का है:
async function loadRemoteBytes (fileUrl) {
const response = await fetch(fileUrl);
return new Uint8Array(await response.arrayBuffer());
}
const record = JSON.parse(await (await fetch('/api/record/42')).text());
const pdfBytes = base64ToBytes(record.attachment.split(',')[1]);
उस स्निपेट पर तीन नोट्स। पहले कॉमा पर स्प्लिट करना ही डेटा-URL हेडर उतारने के लिए काफ़ी है (मीडिया टाइप में कॉमा हो ही नहीं सकती, इसलिए पहला कॉमा हमेशा सेपरेटर होता है)। और अगर वैल्यू डेटा-URL प्रिफ़िक्स के बिना सादा Base64 है, तो स्प्लिट ही छोड़ दें। आख़िरी बात, वह बारा await टॉप-लेवल वाला है, और ब्राउज़र उसे सिर्फ़ मॉड्यूल्स के अंदर ही मानते हैं, इसलिए उस स्निपेट को <script type="module"> टैग की ज़रूरत है, या उन दो पंक्तियों के चारों ओर एक async रैपर की। ईमेल MIME पार्ट्स वही कहानी है, बस कदम और हैं: अटैचमेंट बॉडी 76 चरों पर लाइन रैप किया हुआ Base64 होता है, पर चूँकि atob() व्हिटस्पेस छला देता है, आप उसे रैप टेक्स्ट बिल्कुल वैसा ही सौंप सकते हैं जैसे वह राव मैसेज में आया था - कोई अन-रैपिंग ज़रूरी नहीं। वही एक व्यवहार चुपचाप बहुत सारे रेगैक्स बचा देता है।
WebSocket हैंडशेक की जाँच
ब्राउज़र में डिकोडिंग के कुछ ख़ूबसूरत इस्तेमालों में से एक WebSocket हैंडशेक ख़ुद की जाँच करना है। RFC 6455 माँगता है कि क्लाइंट Sec-WebSocket-Key हेडर भेजे (16 रैंडम बाइट्स, Base64-एन्कोडेड), और सर्वर जवाब में Sec-WebSocket-Accept दे: की के साथ एक फिक्स्ड मैजिक GUID जोड़ी हुई चीज़ का SHA-1 हैश, Base64-एन्कोडेड। अगर वैल्यू मेल नहीं खाती, तो हैंडशेक फेल हो जाता है और कनेक्शन अपग्रेड नहीं होता। इस पूरे अरसाली का मक़्सद यह है कि ऐसा सर्वर जो सिर्फ़ HTTP बोलता है, उससे यह हैंडशेक ग़लती से पूरा नहीं हो सकता - मैजिक GUID इसी लिए मौजूद है कि गणित जानबूझकर ज़्यादा जटिल लगे। और चूँकि ब्राउज़र के पास हैशिंग और एन्कोडिंग दोनों हैं, आप अपेक्षित जवाब ख़ुद गणित कर सकते हैं, जिससे प्रॉक्सी और गेटवे की डीबगिंग एक पंक्ति का काम बन जाती है:
const MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
async function expectedAccept (clientKey) {
const digest = await crypto.subtle.digest(
'SHA-1',
new TextEncoder().encode(clientKey + MAGIC)
);
return btoa(String.fromCharCode(...new Uint8Array(digest)));
}
const accept = await expectedAccept('dGhlIHNhbXBsZSBub25jZQ==');
console.log(accept); // "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
वह आख़िरी पंक्ति कोई संयोग नहीं है - यह RFC का ठीक वही उदाहरण है, बाइट-दर-बाइट दोहराया हुआ। जब आपका गेटवे किसी और चीज़ से जवाब दे, तो अब आपको ठीक-ठीक मालूम है कि समीकरण की कौन-सी तरफ़ झूठ बोल रही है।
HTTP हेडर्स और क्वेरी स्ट्रिंग्स
Base64 HTTP हेडर्स में पसंदीदा है क्योंकि हेडर्स को ASCII होना ज़रूरी है, और सबसे मशहूर मामला Basic ऑथेंटिकेशन है: Authorization: Basic और उसके बाद username:password का Base64 एन्कोडिंग। ऐसे हेडर को पढ़ना (मान लीजिए, यह दिखाने के लिए कि रिक्वेस्ट क्या साथ ला रही है) एक स्प्लिट और एक डिकोड का काम है:
const header = 'Basic YWxpY2U6c2VjcmV0MTIz';
const [user, ...rest] = atob(header.slice(6)).split(':');
const password = rest.join(':');
console.log(user, password); // "alice secret123"
स्प्रेड-एंड-रीजॉइन पैटर्न उस अडचने वाले मगर क़ानूनी मामले का भी निपटारा करता है जहाँ पासवर्ड में कॉलन हो, क्योंकि स्प्लिट पॉइंट हमेशा यूज़रनेम के बाद वाला पहला होता है। वही पैटर्न हर जगह लागू है जहाँ हेडर स्ट्रक्चर्ड वैल्यू छुपाकर लाता है: Proxy-Authorization, कुछ वेनडर-स्पेसिफ़िक हेडर्स, और कभी-कभी कोई कुकी। क्वेरी स्ट्रिंग्स और डीप लिंक्स में Base64 तब दिखता है जब एप्लिकेशन सर्वर के बिना स्टेट शेयर करना चाहता हो: OAuth का state वैल्यू, बहाल किया हुआ सर्च फ़ॉर्म, या "वहाँ से जारी रखें जहाँ मैं रुका था" वाला मार्कर। डिफेंसिवली डिकोड करें - try/catch में लपेटें, क्योंकि वह वैल्यू नेटवर्क की सीमा पार करके आई है और उससे कुछ भी हो सकता है - और जो मिले उसे अनामनेक इनपुट मानें, बिना शर्त।
और यही हमें उस वाक्य तक ले आता है जो हर टर्मिनल के ऊपर पिन किया जाना चाहिए: Base64 एन्क्रिप्शन नहीं है। यह असल में लुकाछिपी तक नहीं है, क्योंकि उस "डीक्रिप्शन" के लिए सिर्फ़ एक फ़ंक्शन कॉल चाहिए जिसे धरती पर हर लैंग्वेज इम्प्लीमेंट करती है। अगर किसी वैल्यू को गुप्त रहना है, तो पहले उसे Base64-एन्कोड करने से वह कम सुरक्षित होती है, ज़्यादा नहीं - यह привैसी का भ्रम बनाती है और मूल तक पहुँचना चाहने वाले के लिए बस एक तिरस्करनीय कदम जोड़ती है।
URL और स्टोरेज में स्टेट
वही लॉजिक हर चीज़ तक बढ़ता है जिसे पेज रिलोड या शेयर लिंक को पार करके बचना है। आम शकियों की लिस्ट: localStorage और sessionStorage के वैल्यू जो स्ट्रक्चर्ड या बाइनरी डेटा साथ लेते हैं, सिंगल-पेज-ऐप रूटिंग स्टेट के लिए URL का हैश फ़्रैगमेंट, और बिल्ड टूल्स द्वारा पेज में उतारे गए कॉन्फ़िग ब्लॉब। स्टोरेज की कहानी एक कन्क्री उदाहरण की हक़दार है, क्योंकि रीड साइड के साथ वह राइट साइड खड़ी है जिसे आपको याद रखनी होगी:
const raw = localStorage.getItem('profile');
const profile = JSON.parse(new TextDecoder().decode(base64ToBytes(raw)));
ध्यान में रखने के लिए तीन बातें। पहली, बजट: ब्राउज़र हर ओरिजिन को लगभग 5 मेगाबाइट localStorage देते हैं, और आपकी स्टोर की गई Base64 स्ट्रिंग मूल डेटा से करीब 33 फ़ीसदी ज़्यादा खाती है, इसलिए 3.5 मेगाबाइट की फ़ाइल चुपचाप 4.6 मेगाबाइट स्टोरेज बन जाती है - और वह स्ट्रिंग मेमोरी में UTF-16 के रूप में रहती है, जो पेज खुले रहते फ़ुटप्रिंट को दोहरा देता है। दूसरी, संगति: दोनों तरफ़ एक ही कैरेक्टर सेट में एन्कोड करें और डिकोड करें, वरना आप बिल्कुल सही बाइट्स स्टोर करेंगे और मोजिबैक पढ़ेंगे। तीसरी, शेयर लिंक: अगर स्टेट URL में सफ़र करती है, तो URL-सुरक्षित वर्णमाला इस्तेमाल करें ताकि वैल्यू कॉपी-पेस्ट को पार कर सके, और उसे छोटा रखें, क्योंकि कुछ हज़ार चरों से लंबे URL पुराने क्लाइंट्स और लॉगिंग टूल्स को घबरा जाने लगते हैं।
जब डेटा टुकड़ों में आता है
कभी-कभी Base64 एक ही स्ट्रिंग के रूप में नहीं आता: WebSocket मैसेज बाउंडरी बीच में से काट देती है, सर्वर-सेन्ट इवेंट स्ट्रीम थोड़ा-थोड़ा बूँदों की तरह डालती है, चंकड अपलोड हर बार कुछ किलोबाइट फ़ीड करता है। फ़्रैगमेंट पर atob() कॉल नहीं किया जा सकता, क्योंकि Base64 ग्रुप 3-बाइट यूनिट्स हैं जिन्हें 4-चर ब्लॉक्स में लिखा जाता है, और ग्रुप के बीच की कट एक लटकती अधूरी चीज़ छोड़ जाती है। पुराने स्टाइल का इलाज यह था कि चरों को बफ़र करते जाएँ जब तक चार का गुणज न मिल जाए, और फिर बफ़र को स्लाइसों में डिकोड करें। 2025 की API इसको साफ़ कर देती है: Uint8Array.prototype.setFromBase64(string, options) डिकोड किए बाइट्स को मौजूदा ऐरे में लिखता है और एक ऑब्जेक्ट लौटाता है जिसमें दो नंबर हैं, read (इसने कितने चर ख़पटे) और written (इसने कितने बाइट्स निकाले)। lastChunkHandling: "stop-before-partial" के साथ, वह सिर्फ़ पूरे ग्रुप डिकोड करता है और अधूरी पूँछ अछूती छोड़ता है, जो स्ट्रीम डिकोडर की ख़ुशी की बिल्कुल वही व्यवहार है:
const parts = [];
let carry = '';
for (const piece of incomingPieces) {
let pending = carry + piece;
for (;;) {
const room = new Uint8Array(8);
const result = room.setFromBase64(pending, {
lastChunkHandling: 'stop-before-partial'
});
parts.push(room.subarray(0, result.written));
pending = pending.slice(result.read);
if (result.read === 0) {
carry = pending;
break;
}
}
}
const size = parts.reduce((sum, part) => sum + part.length, 0);
const bytes = new Uint8Array(size);
let at = 0;
for (const part of parts) {
bytes.set(part, at);
at += part.length;
}
const text = new TextDecoder().decode(bytes);
अंदरूनी लूप को धीरे-धीरे पढ़ें, क्योंकि पूरा पैटर्न वही है: पुराना बाकी-रहा हिस्सा प्लस नया टुकड़ा फ़ीड करें, डिकोडर को उतने पूरे ग्रुप ख़पटने दें जितने फिट हों, result.read चर छिलकर बाकी रहने की गिनती याद रखें, और जब कोई पूरा ग्रुप न बचे (result.read === 0) तो बाकी हिस्से को नया करी बनाकर स्टैश करें और अगले टुकड़े का इंतज़ार करें। Uint8Array(8) बस स्क्रैच बफ़र है - चार चरों का एक ग्रुप ज़्यादा से ज़्यादा तीन बाइट्स देता है, इसलिए आठ काफ़ी उदार है। अंत में, carry वह पकड़े रहता है जो स्ट्रीम ने कभी पूरा नहीं किया, जो या तो आपका एरर सिग्नल है या आपकी "कनेक्शन साफ़ बंद हुआ" जाँच।
कब Base64 न डिकोड करें
कोई भी काम की रेफ़रेंस आपको सिखाती है कि औज़ार रखा कब हो। अगर चैनल के दोनों सिर आप पर हैं, तो राव बाइट्स की तरफ़ जाएँ: डाउनलोड के लिए fetch प्लस response.arrayBuffer(), पिकर फ़ाइलों के लिए file.arrayBuffer(), WebSocket में ArrayBuffer पेलोड्स, और अपलोड के लिए multipart FormData। इनमें से कोई भी Base64 को छूता नहीं है, और डेटा पूरी रफ़्तार मिलता है, न साइज़ का कोई कर, न मेमोरी में स्ट्रिंग का कोई फ़ुटप्रिंट। Base64 ठीक तब अपना ख़र्च कमाता है जब चैनल सिर्फ़ टेक्स्ट का हो: JSON बॉडीज़, क्वेरी स्ट्रिंग्स, ईमेल, स्टोरेज, लीगेसी API, और वह सब जिनका करार कहता है "ASCII या बस्ट"। जिस पल एक बाइट काफ़ी है, वहाँ Base64 स्ट्रिंग प्रिंटेबल होने की ख़ासियत के लिए 33 फ़ीसदी जुर्माना भर रही है, और वह जुर्माना बैंडविड्थ, मेमोरी, और CPU में वसूला जाता है - तीन बिल जो आप सब से बच सकते हैं।
आम डिकोडिंग गड्ढे
सभी ख़ुशहाल रास्तों के बाद, यह है वह लिस्ट कि यह कैसे कड़वाता है, लगभग उसी क्रम में जिसमें आप इसे मिलेंगे:
atob()के रिज़ल्ट को टेक्स्ट मान लेना। यह बाइनरी स्ट्रिंग है।TextDecoderके रास्ते वह टेक्स्ट बनती है; सीधे प्रिंट करें तो मोजिबैक। यही एक उलझन अधिकांश "Base64 काम नहीं करता" रिपोर्टों का कारण है।- यह उम्मीद कि Unicode अपने आप चल जाएगा। "你好" के बाइट्स खुशी-खुशी डिकोड हो जाते हैं, पर तब तक वे बाइट्स ही रहते हैं जब तक कोई डिकोडर आपको न बता दे कि वे UTF-8 हैं। दोनों तरफ़ एक ही कैरेक्टर सेट में एन्कोड करें और डिकोड करें।
- base64url को
atob()में डालना। एक अकेला-या_थ्रो कर देता है। पहले वर्णमाला बदलें, या सही ऑप्शन के साथfromBase64इस्तेमाल करें। - यह मान लेना कि हर लंबी स्ट्रिंग Base64 है। वैध, पैड की गई Base64 स्ट्रिंग की लंबाई चार का गुणज होती है (बिना पैड base64url 2 या 3 पर ख़त्म हो सकता है) और वह ज़्यादा से ज़्यादा एक वर्णमाला इस्तेमाल करती है। चार से भाग देने पर एक बाकी रहना तुरंत फेल है - इस पर try/catch ख़र्च करने से पहले ही जाँच लें।
- वह पैडिंग जिस पर आपने सहमति नहीं की, उसे भरोसा देना। कुछ सिस्टम
=उतार देते हैं, कुछ रखते हैं, और कुछ उसे रैप स्ट्रिंग के बीच में जोड़ देते हैं, जहाँ वह ठीक नहीं है। भेजने वाले से तय करें, फिर तय करें कि ढीले रहना है (atob) या सख़्त (fromBase64)। - ढीले डिकोडर की चुपचाप ख़राबी। डिफ़ॉल्ट
TextDecoderग़लत बाइट्स को U+FFFD से बदल देता है और एक शब्द तक नहीं बोलता। जब डेटा मायने रखता हो, तोfatal: trueसेट करें। - यह मान लेना कि Base64 किसी भी चीज़ की रक्षा करता है। वह नहीं करता। यह एक सिरीलाइज़ेशन फ़ॉर्मैट है, प्लेन टेक्स्ट से एक फ़ंक्शन कॉल दूर, और "हम इसे Base64 करते हैं ताकि यूज़र न पढ़ पाएँ" एक सिक्योरिटी अवाज़ है, कोई कंट्रोल नहीं।
- मेमोरी की भूल। एक मेगाबाइट की डिकोड बाइनरी स्ट्रिंग UTF-16 स्ट्रिंग के रूप में दो मेगाबाइट घेरती है, जबकि वही डेटा
Uint8Arrayमें एक मेगाबाइट का है। बड़े पेलोड के लिए सीधेfromBase64की तरफ़ जाएँ। - हर रेंडर पर फिर से डिकोड करना। कुछ मेगाबाइट डिकोड करना तेज़ है, पर मुफ़्त नहीं - और यह वह काम नहीं जो हर फ्रेम पर करने लायक हो। एक बार डिकोड करें, बाइट्स को कैश करें, कैश से रेंडर करें।
परफ़ॉर्मेंस की नोट्स
छोटी-सी बात: नेटिव डिकोडर तेज़ हैं, और पुराने कोड का धीमा हिस्सा आमतौर पर उनके चारों ओर का JavaScript है, Base64 ख़ुद नहीं। जिन साइज़ों पर बात चलती है, वहाँ तसवीर वही है: 10 मेगाबाइट का पेलोड Uint8Array.fromBase64 से सिंगल-डिजिट मिलीसेकंड में डिकोड हो जाता है; atob अकेला कुछ गुना धीमा है, और क्लासिक फॉलो-ऑन लूप जो चरों को बाइट ऐरे में मैप करता है, वही इनपुट लेकर fromBase64 से करीब बीस गुना ज़्यादा समय लेता है, क्योंकि वह मेन थ्रेड पर करीब तेरह लाख प्रॉपर्टी राइट चलाता है। अमली परिणाम: जहाँ आपकी ताक़ में है fromBase64, वहाँ उसे ही पसंद करें; जहाँ नहीं है, वहाँ atob वाला हेल्पर रखें; लूप में स्ट्रिंग जोड़कर कभी बाइट ऐरे न बनाएँ; और अगर आपको बहुत बड़ा पेलोड प्रोसेस करना ही है, तो डिकोड किया हुआ Uint8Array किसी Web Worker को सौंपने पर विचार करें - बाइट्स बिना कॉपी के ट्रांसफ़र होते हैं, और मेन थ्रेड ख़ाली रहता है UI को 60 फ्रेम प्रति सेकंड पर बनाए रखने के लिए। और गणित की दिशा याद रखें: डिकोडिंग सिकोड़ती है, इसलिए डिकोड बफ़र हमेशा उस स्ट्रिंग से कम मेमोरी लेता है जिससे वह आई। डिकोड करने से मेमोरी कभी ख़त्म नहीं होती; मेमोरी सिर्फ़ तब ख़त्म होती है जब आप स्ट्रिंग और बाइट्स दोनों को ज़रूरत से ज़्यादा देर तक पकड़े रहें।
ब्राउज़र में डिकोडिंग का छोटा सा इतिहास
Base64, आधुनिक वेब के ज़्यादतर हिस्से से बड़ा है, पर ब्राउज़रों के डिकोडरों की कहानी जानने लायक है, क्योंकि वह समझाती है कि इकोसिस्टम में इतने पुराने अवशेष क्यों हैं। atob और उसका भाई btoa उस स्पेसिफ़िकेशन से भी पुराने हैं जो अब उन्हें कवर करता है: WHATWG HTML मानक ने इन्हें फ़रवरी 2011 में परिभाषित किया, जब उनकी लंबे समय से ब्राउज़रों में चली आ रही व्यवहार को रीवर्स-इंजीनियर करके मानक में उतारा गया। इंजन ने इन्हें जल्दी ही शिप कर दिया था: Firefox वर्ज़न 1 से 2004 में, Safari 3, Chrome 4। Internet Explorer ने इन्हें बिल्कुल छोड़े हुए थे, जब तक कि 2012 में IE 10 न आया, यही वजह है कि 2012 से पहले का JavaScript हाथ से बनी Base64 का म्यूज़ियम है - लुक्अप टेबल, String.fromCharCode की कसरतें, और Unicode के लिए वह बेहद मशहूर unescape(encodeURIComponent()) मंत्र, जो दो फ़ंक्शन लैंग्वेज में डीप्रेकेट हो चुके थे और बस पुरानी आदत के बल पर ब्राउज़रों में एक दशक और जीए। फिर कैरेक्टर सेट की परत आई: Encoding मानक से TextEncoder और TextDecoder 2013 और 2017 के बीच उतरे (Firefox 18, Chrome 38, Safari 10.1, और किसी भी IE में कभी नहीं), और आख़िरकार प्लेटफ़ॉर्म को बाइट्स को शब्दों में बदलने का न्यायपूर्ण तरीका मिला। Node.js, जो वर्ज़न 16 (2021) तक ग्लोबल के रूप में atob या btoa रखता ही नहीं था, अपनी जवानी Buffer और दो छोटे npm शिमों के साथ गुज़ारी। और फिर चक्र पूरा हुआ: Firefox 133 (नवंबर 2024) और Safari 18.2 (दिसंबर 2024) ने सबसे पहले Uint8Array.fromBase64, toBase64 और साथियों को शिप किया, और 2025 की दूसरी आधी ने जब Chrome 140 (सितंबर) और Node 25 (अक्टूबर की मध्य) उतरी, तो पूरा सेट पूरा हुआ, और Baseline प्रोग्राम ने उन्हें Newly available चिह्नित किया - पहली बार लैंग्वेज ख़ुद ने - वेब प्लेटफ़ॉर्म नहीं - Base64 को बिल्ट-इन पाया। दशकों पुराना फ़ॉर्मैट अभी-अभी लैंग्वेज की स्टैंडर्ड लाइब्रेरी सुविधा बन गया है, और आने वाले दशक का कोड हेल्परों को यहाँ-वहाँ कॉपी करने से छुट्टी पाएगा।
मज़ेदार तथ्य
- मौजूदा सबसे तेज़ "क्या यह Base64 तक है?" टेस्ट
string.length % 4 === 0है। हर वैध, पैड की गई Base64 स्ट्रिंग पास हो जाती है; बाक़ी सब अनजाने हैं। atob('')''लौटाता है। ख़ाली स्ट्रिंग एकमात्र इनपुट है जिसमें कोई बाइट नहीं, और वह पूरी पाइपलाइन से साफ़-साफ़ रउंड ट्रिप करती है - कभी किसी ख़ास मामले की ज़रूरत नहीं, कभी नहीं।- WebSocket का मैजिक GUID,
258EAFA5-E914-47DA-95CA-C5AB0DC85B11, RFC में बस चुना हुआ एक फिक्स्ड वैल्यू है, जिसका चयन इसीलिए कि कोई सादा HTTP सर्वर कभी ग़लती से हैंडशेक पूरा न कर सके। यह प्रोटोकॉल इंजीनियरिंग का सबसे मशहूर कन्स्टेंट है जिसे कभी कोई जनरेट नहीं करता। - Chrome और Firefox उसी फेलियर पर वही एक्सेप्शन थ्रो करते हैं, पर मैसेज अलग-अलग के होते हैं।
error.nameपर कैच करें, मैसेज स्ट्रिंग पर नहीं, वरना आपके एरर हैंडलिंग में ब्राउज़र की लहजा निकलेगी। - एक मेगाबाइट की बाइनरी स्ट्रिंग मेमोरी में दो मेगाबाइट का वज़न रखती है, क्योंकि JavaScript स्ट्रिंग्स UTF-16 होती हैं: हर डिकोड बाइट एक बाइट का इस्तेमाल न होने वाला अतिरिक्त हिस्सा साथ लाता है।
Uint8Arrayमें ऐसा कर नहीं है। - "Data URI" एक पुराना, हटा दिया गया नाम है। WHATWG ने बड़े URI-से-URL सामंजस्य का हिस्सा बनकर इसे "data URL" नाम दिया, इसलिए स्पेसिफ़िकेशन, पोस्ट्स, और पैकेज नामों में दोनों स्पेलिंग मिलेंगी।
- RFC 4648 टेस्ट वेक्टर की एक टेबल लेकर आता है - 'f', 'fo', 'foo', 'foob', 'fooba', 'foobar' और साथी, हर एक की अपनी ज़ाने-पहचान वाली एन्कोडिंग के साथ - जिनके हिसाब से डिकोडर बनाने वाले बीस साल से जाँच कर रहे हैं। अगर आपका डिकोडर उन पंक्तियों से पास हो जाता है, तो वह लगभग ज़रूर सही है।
- कंप्यूटिंग के इतिहास में सबसे ज़्यादा बनी Base64 स्ट्रिंग लगभग ज़रूर
aGVsbG8=है, "hello" की एन्कोडिंग। पृथ्वी का हर "शुरू कैसे करें" ट्यूटोरियल, हर टेस्ट सूट, और हर Stack Overflow जवाब अपना वोट डालता है।
समापन
तो ब्राउज़र में डिकोडिंग की पूरी कला एक पेज पर समा जाता है: atob() तुरंत, बुज़दिल, और सार्वभौमिक डिकोड के लिए; TextDecoder बाइट्स को उन शब्दों में बदलने के लिए जो आपको वाक़ई चाहिए, fatal: true के साथ जब डेटा मायने रखता हो; और Uint8Array.fromBase64 आधुनिक, सख़्त, तेज़ रास्ते के लिए जो स्ट्रिंग को बिल्कुल छोड़ देता है। इनके बीच, वैरिएंटों के नाम और नियम हैं: जो कुछ भी URL में सफ़र करता है उसे base64url, पैडिंग जो हो भी सकती है और न हो भी, और व्हिटस्पेस जिसे पुराना डिकोडर चुपचाप ख़त्म कर देता है। और सबके नीचे, दो नज़रिया: बाइट्स, टेक्स्ट नहीं है, और टेक्स्ट, सीक्रेट नहीं है। इरादे से डिकोड करें, भरोसे से पहले सत्यापित करें, और जब चैनल मंज़ूर दे, तो Base64 छोड़कर बाइट्स ही लें।
यात्रा का दूसरा आधा हिस्सा - आपकी बाइट्स और टेक्स्ट को उस प्रिंटेबल स्ट्रिंग में बदलना जिससे सारा यह खेल शुरू हुआ - विस्तार से JavaScript में Base64 एन्कोडिंग के संगी गाइड में कवर किया गया है, जो नीचे लिंक किया गया है।
अंतिम अपडेट: 2026-09-08
संबंधित लेख: JavaScript/Browser में Base64 एन्कोडिंग: एक सम्पूर्ण गाइड