文件操作编码知识
前言
项目开发中,遇到一个需求:后端返回 json,json中某个字段存有文件的 base64值,然后前端将文件导出下载,即
{
"file": "文件base64编码值"
}
其实最简单的应该就是将 base64补上前缀,就可以传给 a标签下载了,但是当时就是脑子一抽,非要搞点花里胡哨的,结果就成了这样:
- 先在前端模拟生成文件的 base64编码值
<input type='file' @change='change'>
change (e) {
let file = e.target.files[0]
let reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => {
let dataURL = reader.result
let base64Str = dataURL.split(',') // , 后面的才是数据对应的 base64码,此处的 base4字符串与后端返回的一致
....
}
}
- 将 base64含有的实际二进制数据存入 ArrayBuffer
// 先将 base64解码, TextDecoder并不能解码 base64,只能用 atob
let data = atob(base64)
let len = data.length
// 为什么用 Unit8Array? 因为 atob是使用 Latin1编码,即 1字节映射一个字符,将字符编码再传入 Uint8Array中,方能生成相同的二进制数据
let uint8 = new Uint8Array(len)
while(n) {
uint8[n-1] = String.prototype.charCodeAt.call(data[n-1], 0)
}
// 放入 Blob再生成 url, type代表 blob的数据类型
let blob = new Blob([uint8], {type: 'application/vnd.ms-excel'})
let url = URL.createObjectURL(blob)
// 利用 a标签下载
let a = document.createElement('a')
a.href = url
a.download = '导出.xls'
a.click()
如果你看懂了,那么你可以点击右上角的关闭按钮,咱们拜拜。如果没有看懂,那说明你对其中一些细微知识不甚了解,接下来我们就开始好好聊聊了。
上面代码片段中涉及的主要知识点有:
- FileReader的用法
- 二进制数组 ArrayBuffer、TypeArray的使用
- atob编码原理(包括 Latin1编码、base64编码)
前面两个在《前端文件操作基础知识(一)文件对象篇》有介绍,最后一个将在本篇进行阐述。
javascript中的编码
ES6默认采用 utf-16编码,并且是小端字节序,比如:'a'的 unicode编码为 97, 它的二进制表示为 0110 0001,而在 js中,由于是 utf-16编码且为小端字节序, 那么将存它将被存储为 0110 0001 0000 0000 共两个字节,即
a -> 97 -> 0110 0001 -> 0110 0001 0000 0000
TextEncode和 TextDecode
既然 ES6是采用 utf-16编码的,而我们常用的却是 utf-8编码,那你肯定就会想怎么才能在 ES6中进行 utf-8编码呢,对吧?
这里介绍两个方法:
- TextEncoder
编码,按指定编码格式(默认 utf-8)对字符进行编码,并返回 uint8Array
let encoder = new TextEncoder() // 参数默认 utf-8,可以为 utf-16, gbk等其它编码格式
let str = '需要编码的字符串'
let uin8 = encoder.encode(str) // 返回 uint8Array
- TextDecoder
解码,按指定编码格式(默认 utf-8)对 ArrayBuffer/ TypedArray 进行解码
let decoder = new TextDecoder() // 参数默认 utf-8,可以为 utf-16, gbk等其它编码格式
let uint8 = new Uint8Array([97, 98, 99])
let str = decoder.decode(uint8) // 'abc'
总结:借助 TextEncoder和 TextDecoder实现字符串与二进制的互相转化。
base64 编码原理
base64编码一共有64个字符加一个垫字的 '=', 共65个字符,所以只需要 6bit便能刚好映射完 base64字符(2^6 = 64)。
base64编码流程大体如下:
- 1、将二进制数据按每6位为一组进行分组
- 2、最后一组数据则补足6位
- 3、每一组数据前面补两个0
- 4、按 base64编码映射成字符,最后一组数据不足6位补的0如果为2个,则 base64最后还需加一个'=',如果补了4个,则加两个'='。
举个例子,"Ma"的 base64编码:
-> 在 utf-8编码下,其二进制数据为:0100 1101 0110 0001
-> 分组:010011 010110 000100 (注意,最后一组补了2个0)
-> 前面补0:00010011 00010110 00000100
-> 按 base64映射:19=>T 22=>W 4=>E
-> 最后为:TWE=
通过 window.btoa('Ma') 测试成功!
escape、encodeURI和 encodeURIComponent
- escape (已不提倡使用)
除了 ASCII字母、数字和符号"@+-*/._" 以外,其它将编码为 unicode值(16进制)。unicode编码在1字节内,格式为%xx,超过1字节,格式为%uxxxx
escape('春节') // %u6625%u8282
对应的解码:
unescape('%u6625%u8282') // 春节
- encodeURI
真正用于编码 URL的函数。除了一些常见的符号外,对其它一些网址中有特殊含义的符号 "; / ? : @ & = + $ , #" 也不进行编码。编码方式为:unicode值 -> utf-8编码成二进制 -> 16进制表示 -> 前面再加上%
// "春节"的 utf-8编码下16进制表示为 E6 98 A5 E8 8A 82
encodeURI('春节@') // "%E6%98%A5%E8%8A%82@"
对应的解码:
decodeURI('%E6%98%A5%E8%8A%82') // '春节'
- encodeURIComponent
编码方式与 encodeURI大体一致,只是该方式会对 "; / ? : @ & = + $ , #"这些特殊符号也进行编码
encodeURIComponent('春节@') // "%E6%98%A5%E8%8A%82%40"
对应的解码:
decodeURIComponent("%E6%98%A5%E8%8A%82%40") // '春节@'
总结:encodeURI和 encodeURIComponent将字符编码为 utf-8编码的16进制表示,即转化后所有都是 ascii码。
| ascii字符 | 特殊符号 | 其他符号 | |
|---|---|---|---|
| encodeURI | × | × | √ |
| encodeURIComponent | x | √ | √ |
encodeURI设计是为了对整个 uri进行编码,而 encodeURIComponent设计是对 uri查询参数进行编码,而前端使用场景更多的是对 url的查询参数进行编码,所以主用 encodeURIComponent。
Latin1编码(别名:ISO-8859-1)
Latin1字符集是 ascii字符集的扩展,是 8bit大小的字符集,字符编码在 0 - 255之间。
ascii、Latin1和 utf-8之间的关系:
ascii是 Latin1、utf-8的子集,ascii编码的文件可以被 Latin1或 utf-8解码;
Latin1后面的字符被 ascii支持,所以 ascii解码 Latin1可能会报错
Latin1后面的字符在 utf-8中是以2字节存储的,所以如果用 utf-8解码 Latin1会乱码
atob和 btoa原理
atob和 btoa均是基于 Latin1编码实现的,所以字符编码超过 255将会报错
- atob
用于将 base64解码为字符串, 内部流程是:ascii字符 -> base64解码成二进制 -> Latin1解码为字符
atob("YXBwbGU=") // 'apple'
- btoa
用于将字符编码为 base64, 内部流程是:Latin1编码字符为二进制 -> base64编码 -> ascii字符
// 使用 ascii字符串时,可正常编码
btoa('apple') // "YXBwbGU="
// 使用中文时,由于超出 Latin1编码范围,则报错
btoa('中文') // 报错 The string to be encoded contains characters outside of the Latin1 range.
由上面可以看出,如果通过 btoa编码中文将会报错,那应该怎么将中文编码为 base64呢,需要满足如下两点:
- 1、能够将中文编码为 Latin1所允许的字符
- 2、转化之后可以再转换回中文
那么,有什么方法满足上面的要求呢?对的,就是 encodeURIComponent/ encodeURI。通过这两个方法编码中文后,编码的格式为%xx,均属于 Latin1允许字符内,并且有个 % 可以作为标识,解码回中文。故而含有中文的 base64编码应该为:
let str = '中文abc'
let baseData = btoa(encodeURIComponent(str)) // 编码成 base64
let decodeStr = decodeURIComponent(atob(baseData)) // 解码成字符串 '中文abc'
charCodeAt和 fromCharCode
- charCodeAt
获取字符在字符编码
let code = 'abc'.charCodeAt(0) // 97
- fromCharCode
由字符编码返回字符
let str = String.fromCharCode.call(97) // 'a'
Gitalking ...
Be the first guy leaving a comment!