前言
Base64 是一种基于 64 个可打印字符来表示二进制的表示方法。具体的 64 个字符如下图所示:
出现背景
早期邮件传输协议基于 ASCII 文本,对于诸如图片、视频等二进制文件处理并不好。
ASCII 主要用于显示现代英文,到目前为止只定义了 128 个字符,包含控制字符和可显示字符。
为了解决上述问题,Base64 编码顺势而生。
基础使用
在浏览器环境下,可以使用 window.atob 和 window.btoa 方法,进行 Base64 和 UTF-8 之间的转化:
const utf8ToBase64 = window.btoa('Hello World!');
console.log(utf8ToBase64); // base64: SGVsbG8gV29ybGQh
const base64ToUtf8 = window.atob(utf8ToBase64);
console.log(base64ToUtf8); // UTF-8: Hello World!
在 Node.js 环境下,可以通过 Buffer 来完成两者之间的转化:
const strToBase64 = Buffer.from('Hello World!', 'utf-8').toString('base64');
console.log('base64: ', strToBase64); // base64: SGVsbG8gV29ybGQh
const str = Buffer.from(strToBase64, 'base64').toString('utf8');
console.log('utf-8: ', str) // utf-8: Hello World!
实现原理
Base64 编码的过程如下:
- 首先对待编码字符串进行每 3 个字节分组,如果当前分组不够 3 个字节,则通过补零来构成 3 字节一组。
- 将每组的 24 位按照 6 位划分,得到 4 个 6 位二进制数组。
- 对每个 6 位二进制数组进行高位补零,形成 8 位二进制数数组。
- 查表获取每个字节的十进制对应的字符。
- 将(1)中补充的字节数兑换成 = 的个数,拼接到(4)得到的完整字符串的尾部。
以 a 字符为例。
a 对应 1 个字节,如果要完成分组,那么就需要用零补足两个字节:
0 1 1 0 0 0 0 1
----------------
0 0 0 0 0 0 0 0
----------------
0 0 0 0 0 0 0 0
接下来需要将它们再按照 6 位划分:
0 1 1 0 0 0
-----------
0 1 0 0 0 0
-----------
0 0 0 0 0 0
-----------
0 0 0 0 0 0
再进行高位补零补足 8 位,形成一个字节:
0 0 0 1 1 0 0 0 --> 24
---------------
0 0 0 1 0 0 0 0 --> 16
---------------
0 0 0 0 0 0 0 0 --> 0
---------------
0 0 0 0 0 0 0 0 --> 0
再根据每个字节的十进制值查表,得到字符串 YQ,由于用零补充了两个字节,所以加上两个 = ,那么最终转化的结果为:
YQ==
特点
从上述实现原理中,可总结出 Base64 编码的几个特点:
- Base64 编码结果的长度永远是 4 的整数倍。
- Base64 编码结果至多包含两个 = 字符。
- Base64 编码使原文的大小增加了 1/3 倍。
应用场景
Base64 编码的第一种应用场景就是前面提到的邮件传输协议,不过随着时代的发展,邮件传输协议已经支持直接传输二进制数据,不再需要 Base64 或者其它编码方式。
HTTP2 也改进为二进制格式传输数据,使得协议解析起来更加高效。
第二种应用场景:加密内容的输出。
客户端在发起请求时,通常需要将内容进行加密处理,而最终加密输出的内容就是 Base64 编码格式。这里有一个特别需要注意的地方,当标准 Base64 编码的内容放在 URL 中传输的时候会出特殊的情况导致服务端解密失败。
URL编码器会把标准 Base64 编码中的 “/” 和 “+” 字符变为形如 “%XX” 的形式。
所以客户端一般会将标准 Base64 编码中的 + 和 / 替换为 URL 安全的字符,例如 - 和 _ 。
第三种应用场景:图片资源的显示。
在浏览器环境下,不仅可以通过标准的 HTTP 或者 HTTPS 链接来显示图片,还可以通过 Data URLs 来显示图片:
<img src="data:image/gif;base64,R0lGODlhMwAxAIAAAAAAAP///
yH5BAAAAAAALhbstxgzADEAAAK8jI+pBr0PowytzotTtbm/DTqQ6C3hGX
ElcraA9jIr66ozVpM3nseUvYP1UEHF0FUUHkNJxhLZfEJNvol06tzwrgd
LbXsFZYmSMPnHLB+zNJF1215+SOf50+6rG7lKOjwV1ibGdhHYRVYVJ9Wn
k2HWtLdIWMSH9lfyODZoZTb4xdnpxQSEF9oyOWIqp6gaI9pI1Qo7BijbF
ZkoaAtEeiiLeKn72xM7vMZofJ23zJys2UxsCT3kO229LH1tXAAAOw==">
webpack 中的 url-loader 就提供了将图片转化为 Data URLs 的功能,一般会设置图片的大小限制,这主要是因为 Base64 处理之后的大小会增加 1/3 倍,所以图片太大的话,反而得不偿失。
这种将图片内联到 HTML 或者 CSS 的方式,可以有效地减少请求数量,从而优化站点的网络请求。
但是该方式也存在缺陷:
- 体积增大,需要权衡对 HTML 和 CSS 的影响。
- 无法充分利用缓存,依赖其内联的 HTML 和 CSS 文件的缓存。