【Base64简易版】迫于经常打交道,整理了一波相关概念

1,223 阅读3分钟

先说常见使用场景

  • 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码
原始字符Hello!!
Ascii码721011081081113333

比如Hello就变成了72、101、108、108、111的ascii码

  • 3、再将ascii转换成二进制
原始字符Hello!!
Ascii码721011081081113333
二进制0100 10000110 01010110 11000110 11000110 11110010 00010010 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,才算生成完毕。

这里有张图比较清晰

image.png

原文链接为: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码对照表

Ascii码对照表

最后

囫囵吞枣,只理清了大概的编码思路,有不对、不严谨的地方还请指出