先说常见使用场景
-
1、减少http请求,将图片进行编码后,作为src的值
<img alt="Embedded Image" src="data:image/png;base64,iVBORw0KGgoAFFFASDWQQWQWIA..." /> -
2、不易读,对一些明文数据进行编码
window.btoa('mypassword') // "bXlwYXNzd29yZA==" -
3、将画布导出生成图片下载
let url = canvas.toDataUrl() // 返回一个base64格式的字符串 let a = document.createElement('a') a.setAttribute('download', 'canvas.png') a.href = url a.style.display = 'none' document.body.appendChild(a) a.click()
浏览器Window内置方法
编码:window.btoa(Binary to Ascii)
这个api我一直ab记不清谁在先,查了全称后稍微有印象了。
// 编码
window.btoa('mypassword')
// "bXlwYXNzd29yZA=="
解码:window.atob(Ascii to Binary)
// 解码
window.atob('bXlwYXNzd29yZA==')
// "mypassword"
So,它究竟是什么东西,为何字符串也能当图片显示?
首先,我们都知道,像java、javascript这种高级语言,经过高级语言——>汇编语言——>机器语言逐次翻译,最终实际只是一堆二进制的数据。
所以图片本质上一直是一堆数据罢了,只是感官上会产生误解。
当我们 window.btoa('Hello!!') 进行编码时
- 1、首先判断编码的字符串是否在Ascii码中,如果是继续,否则报错
- 2、将字符串逐个换算成Ascii码
| 原始字符 | H | e | l | l | o | ! | ! |
|---|---|---|---|---|---|---|---|
| Ascii码 | 72 | 101 | 108 | 108 | 111 | 33 | 33 |
比如Hello就变成了72、101、108、108、111的ascii码
- 3、再将ascii转换成二进制
| 原始字符 | H | e | l | l | o | ! | ! |
|---|---|---|---|---|---|---|---|
| Ascii码 | 72 | 101 | 108 | 108 | 111 | 33 | 33 |
| 二进制 | 0100 1000 | 0110 0101 | 0110 1100 | 0110 1100 | 0110 1111 | 0010 0001 | 0010 0001 |
- 关键4、再将第三步的二进制串按顺序取6个分割
// 原二进制:
// 0100 1000 0110 0101 0110 1100 0110 1100 0110 1111 0010 0001 0010 0001
// 经过按6个依次分割:
// 010010 000110 010101 101100 011011 000110 111100 100001 001000 010000 000000 000000
你可能发现了,经过分割后,二进制串变长了。这是因为按6个截取的时候,会出现余数,这个时候就要进行补零(zero fill),直到该二进制串长度同时满足对6跟8求余等于0,才算生成完毕。
这里有张图比较清晰
原文链接为:www.cnblogs.com/peterYong/p…
-
5、经过前面几步得到新的二进制串后,再将二进制串进行还原成Ascii码
-
6、将得到的Ascii码映照到Base64表中
-
7、约定base64的结尾
由于第四步可能出现补零操作,标准通常约定结尾的A都用=来替换,表示原始字符的结尾;这也是为什么我们经常在一段base64字符串中看到=的原因。
Javascript版本的编码实现
// 初始化base64映射表
function initBase64Map() {
let map = {};
for (let i = 0; i < 26; i++) {
map[i] = String.fromCharCode(65 + i);
map[26 + i] = String.fromCharCode(97 + i);
if (i < 10) {
map[52 + i] = i;
}
}
map[62] = "+";
map[63] = "/";
return map;
}
const base64Map = initBase64Map()
class Data {
constructor() {}
// 校验是否单字节数据
static isValid(str) {
for (let i = 0; i < str.length; i++) {
const cur = str[i];
if (cur.charCodeAt() > 126) {
return false;
}
}
return true;
}
// 进行补零
static _convertToBinary(asciiCode) {
// 先将十进制转二进制,再fill zero补零
let code = parseInt(asciiCode).toString(2);
while (code.length < 8) {
code = "0" + code;
}
return code;
}
static convertToBinaryData(strData) {
let result = "";
this._formatBinary = "";
for (let i = 0; i < strData.length; i++) {
const str = strData[i];
const asciiCode = str.charCodeAt();
const binaryCode = Data._convertToBinary(asciiCode);
result += binaryCode;
this._formatBinary += binaryCode + " ";
}
return result;
}
// 由于原8字节的数据拆成6字节,不足8字节跟6字节的二进制,需末尾补零
static fillZero(binary) {
while (binary.length % 6 !== 0 || binary.length % 8 !== 0) {
binary = binary + "0";
}
return binary;
}
static toBase64(binaryCode) {
let result = "";
for (let i = 0; i < binaryCode.length; i += 6) {
const binary = binaryCode.substring(i, i + 6);
result += base64Map[parseInt(binary, 2)];
}
let arr = result.split("");
for (let i = arr.length - 1; i > 0; i--) {
if (arr[i] === "A") {
arr[i] = "=";
} else {
break;
}
}
return arr.join("");
}
static encode(str) {
if (!this.isValid(str)) {
return new Error("字符串含有非ascii范围内的字符");
}
let binaryCodeOrigin = Data.convertToBinaryData(str);
let fillZeroBinaryCode = Data.fillZero(binaryCodeOrigin);
let result = Data.toBase64(fillZeroBinaryCode);
return result;
}
}
window.btoa('Hello!!!') === Data.encode('Hello!!!')
相关
知道为什么window.btoa('中文')的时候会报错吗?
什么是Ascii码
是一套基于拉丁字母的字符编码,共收录了 128 个字符,用一个字节就可以存储
Ascii码对照表
最后
囫囵吞枣,只理清了大概的编码思路,有不对、不严谨的地方还请指出