Base64 是一组相似的二进制到文本(binary-to-text)的编码规则,使得二进制数据在解释成 radix-64 的表现形式后能够用 ASCII 字符串的格式表示出来。Base64 这个词出自一种 MIME 数据传输编码。
浏览器
现代浏览器原生支持base64的编解码,在控制台和js worker中都可使用,其提供了2个方法:atob()
btoa(),分别用于解码和编码.
btoa()
从 String 对象中创建一个base-64编码的ASCII字符串,其中字符串中的每个字符都被视为一个二进制数据字节。
由于这个函数将每个字符视为二进制数据的字节,而不管实际组成字符的字节数是多少,所以如果任何字符的码位超出 0x00 ~ 0xFF 这个范围,则会引发 InvalidCharacterError 异常。请参阅 Unicode_字符串 ,该示例演示如何编码含有码位超出 0x00 ~ 0xFF 范围的字符的字符串。
因此,该函数只能将ascll字符串编码成base64格式,对于ascll以外的字符串则会报错
错误提示要编码的字符中包含了latin1编码以外的字符,这些字符是不能被编码的。
latin1: Latin-1 代表 ISO-8859-1。 此字符编码仅支持从 U+0000 到 U+00FF 的 Unicode 字符。 每个字符使用单个字节进行编码。 超出该范围的字符会被截断,并映射成该范围内的字符。
解决方法是将字符串中的非ascll字符进行转义,然后再进行编码
// unicode to base64
function utoa(str) {
return window.btoa(unescape(encodeURIComponent(str)));
}
上述代码来源于MDN文档WindowOrWorkerGlobalScope.btoa()
encodeURIComponent用于将字符串中非ascll字符转义成格式为"%加字符的ASCII码"的十六进制转义序列,常用于URI的处理。
处理后的字符串完全由ascll字符组成,再由unescape()将其中的十六进制转义序列转换为其表示的字符,最后再由btoa()编码成base64,不过这里的unescape()是非必要的,一方面该方法已被web标准弃用,二是经过转义的字符串已经能够被btoa()编码。
function utoa(str) {
return window.btoa(encodeURIComponent(str));
}
// unicode to base64 by
function utoa(str) {
return window.btoa(unescape(encodeURIComponent(str)));
}
// base64 to unicode
function atou(str) {
return decodeURIComponent(escape(window.atob(str)));
}
atob()
与上面同理,直接使用atob()转换一个不是全由ascll字符组成的字符串编码成的base64字符串时,会报与上述同样的错误。
MDN文档WindowOrWorkerGlobalScope.btoa()的方法
// base64 to unicode
function atou(str) {
return decodeURIComponent(escape(window.atob(str)));
}
escape()是非必要的
function atou(str) {
return decodeURIComponent(window.atob(str));
}
Polyfill
有不少PC项目还需要兼容IE9以下版本,所以,我们可以专门针对这些浏览器再引入一段ployfill脚本或者一个JS文件即可。
另一个ployfill实现
// Polyfill from https://github.com/MaxArt2501/base64-js/blob/master/base64.js
(function() {
// base64 character set, plus padding character (=)
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
// Regular expression to check formal correctness of base64 encoded strings
b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
window.btoa = window.btoa || function(string) {
string = String(string);
var bitmap, a, b, c,
result = "",
i = 0,
rest = string.length % 3; // To determine the final padding
for (; i < string.length;) {
if ((a = string.charCodeAt(i++)) > 255 ||
(b = string.charCodeAt(i++)) > 255 ||
(c = string.charCodeAt(i++)) > 255)
throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");
bitmap = (a << 16) | (b << 8) | c;
result += b64.charAt(bitmap >> 18 & 63) + b64.charAt(bitmap >> 12 & 63) +
b64.charAt(bitmap >> 6 & 63) + b64.charAt(bitmap & 63);
}
// If there's need of padding, replace the last 'A's with equal signs
return rest ? result.slice(0, rest - 3) + "===".substring(rest) : result;
};
window.atob = window.atob || function(string) {
// atob can work with strings with whitespaces, even inside the encoded part,
// but only \t, \n, \f, \r and ' ', which can be stripped.
string = String(string).replace(/[\t\n\f\r ]+/g, "");
if (!b64re.test(string))
throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");
// Adding the padding if missing, for semplicity
string += "==".slice(2 - (string.length & 3));
var bitmap, result = "",
r1, r2, i = 0;
for (; i < string.length;) {
bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12 |
(r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++)));
result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255) :
r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255) :
String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255);
}
return result;
};
})()
类型化数组转base64
有时候后端传给我们一个二进制流的文件资源,我们需要使用一个类型化数组来进行存储和读取。
ArrayBuffer我们叫它类型化数组,它的诞生就是为了解决一个问题:操作二进制数据。
只由0和1组成的二进制数据往往是非常巨大的,上千个字节可以说司空见惯,传统的Array这时候处理起二进制数据起来就显得非常低效,所以ArrayBuffer出现了,它作为一块专用的内存区域存放在栈中,取数据非常快。
我们现在通过new ArrayBuffer(10)初始化一个buffer实例,看看会得到什么。
let buffer = new ArrayBuffer(10);
console.log(buffer);
ArrayBuffer(10) {}
[[Int8Array]]: Int8Array(10) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[[Int16Array]]: Int16Array(5) [0, 0, 0, 0, 0]
[[Uint8Array]]: Uint8Array(10) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
byteLength: 10
__proto__: ArrayBuffer
可以看到在ArrayBuffer中,主要存放了几个视图,Int8Array表示8位有符号整数数组,Int16Array表示16位有符号整数数组,Uint8Array则表示8位无符号整数数组。
当然,如果比如说我们想取出Int8Array这个数组来,是不能直接通过buffer.Int8Array来取的。这是因为ArrayBuffer不能直接通过下标去读写,我们需要构造相关类型数组实例。
const myUint8Array = new Uint8Array(buffer)
得到数组值后,怎样才能将其转换成base64呢? 很简单,使用String.fromCharCode这个函数,它接受的参数为一堆代码单元序列,输出一个普通字符串。而我们刚刚得到的类型化数组,里面存放的正是代码单元。
const myStr = String.fromCharCode(...myUint8Array)
现在类型数组里的值已经被全部转换成字符串了,我们再使用btoa()将其编码成base64字符串
node
node环境中虽然没有提供专门方法去进行base64的编解码,但是我们能利用buffer.toString()很轻松的完成,不仅仅是处理字符串,处理文件也非常方便。
处理字符串
function stringToBase64(str){
let base64Str = Buffer.from(str).toString('base64')
return base64Str;
}
function base64ToString(base64Str){
let str = Buffer.from(base64Str,'base64').toString()
return str
}
处理文件
举个栗子
对一幅图片进行base64编解码
//编码
const fs = require('fs')
const fileToBase64 = fs.readFileSync('./img.png')
console.log(fileToBase64.toString('base64'))
//输出
iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAgAElEQVR4Xuy9CZQdZ3Uu+tVcdeah5251t8bWZEuyJGxjecJDGHMJYEISwiPAu7DyXt66YSUvyc3wlEByIXkk910ePCC5gSQ3IYlJwg0EG4ONsS3Pkm1Jlq1Z6kE9nXmqU/Nbe1cdWZiEaAqms+p4tVtSV9X5z66/vt7Dt78tIH7FFogtEFtghVhAWCHrjJcZWyC2QGwBxIAVb4LYArEFVowFYsBaMbcqXmhsgdgCMWDFeyC2QGyBFWOBGLBWzK2KFxpbILZADFjxHogtEFtgxVggBqwVc6vihcYWiC0QA1a8B2ILxBZYMRaIAWvF3Kp4obEFYgvEgBXvgdgCsQVWjAViwFoxtypeaGyB2AIxYMV7ILZAbIEVY4EYsFbMrYoXGlsgtkAMWPEeiC0QW2DFWCAGrBVzq+KFxhaILRADVrwHYgvEFlgxFogBa8XcqnihsQViC8SAFe...
//解码
const base64Tofile = Buffer.from(fileToBase64.toString('base64'),'base64')
//输出为img2.png
fs.writeFileSync('img2.png', base64Tofile);
编码后的文件要能被浏览器识别,必须根据其
MIME type加上相应类型的前缀,如png图片:data:image/png;base64,,txt文本:data:text/plain;base64,
js-base64库
详情请看github文档,使用起来也非常方便