代码:https://github.com/374632897/demos/blob/master/encrypt/base64.js
在平时的工作中,base64使用场景比较多,比如文件上传。那么,base64到底是个什么东西?
通常来讲,我们平时使用的字符集是utf-8,而对于JavaScript中的binary string 而言,其使用的是ASCII扩展字符集。标准的ASCII字符集只有128个字符(2^7),而ASCII扩展字符集则包含了256个字符。
使用String.prototype.charCodeAt 方法,可以获取到相应字符的码点。
而base64,其字面意思就是使用64个字符来对输入内容进行描述(实际上应该是65个字符,A-Za-z0-9,以及+/总共64个字符,但是最后可能还需要使用=进行补位)。对于二进制字符串来说,可能包含256个字符,所以当使用base64来对二进制字符串进行编码的时候,就需要将256个字符描述的内容使用64个字符来进行描述。
简单来讲,就是把3个8bit的数据用4个6bit的数据来进行表示。
我们知道, 256 = 2 ^8,64 = 2 ^6, 因此当我们把二进制字符串的每个字符使用二进制来进行描述,并确保其填充到8位之后,再将相应的字符串以6位为单位进行分组,针对分组后的字符串再进行填充,填充到8位,这样就可以用64位字符编码来对其进行描述了。
在电子邮件中,每隔76个字符,还需要添加一个回车来进行换行。
以下以字符串“He”为模板进行步骤描述:
输入字符串 ‘He’
针对每个字符串,调用
String.prototype.charCodeAt
方法,获取其码点1
2const charsCode = 'He'.split('').map(item => item.charCodeAt(0))
// [72, 101]将获取到的码点用二进制来表示,并填充成8位
1
2
3const binaryAry = charsCode.map(item => item.toString(2))
// ["1001000", "1100101"]
// ["01001000", "01100101"]将获得的二进制字符串数组进行合并并以6个为单位进行分组,在这里需要注意的是,如果合并后的二进制字符串不是3的整数的话,那么需要在末尾用0来进行填充
1
2
3
4["01001000", "01100101"]
'0100100001100101'.length // 16
'010010000110010100' // 末尾填充
// 010010 000110 010100针对分组后的字符串,使用0将其填充到8位1
2// 010010 000110 010100
// 00010010 00000110 00010100将最后得到的二进制字符串转换为十进制
1
2
3// 010010 000110 010100
'010010 000110 010100'.split(' ').map(item => parseInt(item, 2))
// [18, 6, 20]将得到的十进制数字从base64字母表中进行查找,得到对应的字符
1
2a.map(item => b64Ary[item])
// ["S", "G", "U"]判断得到的结果是否为4的倍数,如果不是,使用=号进行补位 或者说 原数据的长度是否为3的倍数,如果不是则补位(为1,则为两个==,为2则为1个=)
1
2
3// ["S", "G", "U"]
// SGU
// SGU=
最后,转换完成,将 He 转换为了 SGU=
从上面的步骤来看,可能我们会有以下疑问:
- 第4步中为什么合并后的字符串需要是3的倍数?
因为需要将3个字节编码为4个字节
- 第8步中为什么最后的base64字符串需要为4的倍数?
同上,最后一段base64编码代表的是若干个字符,而编码规则便是以4为单位的
- 以上考虑的是针对二进制的,那么针对utf-8的字符串,要做base64处理的话,应该怎么做呢?
在使用base64编码后,编码后的数据相比于原数据会多出大概1/3的内容,因为原本的3个字符变成了4个字符,因此(4 - 3)/ 3 = 1/3
**
另外一种实现方式:https://github.com/mathiasbynens/base64/blob/master/src/base64.js
带注释的版本
1 | var encode = function(input) { |
参考:
- 维基百科 - Base64