那些前端二进制操作API
一直以来,前端的工作主要涉及的是字符串操作,而对二进制的数据接触较少。但是这种需求却一直存在着,尤其是HTML5之后,随着web应用越来越复杂,File,Blob,TypedArray这些API的出现使得前端对二进制的操作更加方便。
atob,btoa
这两个函数的应用场景之一是解密大佬留下的微信号😂,函数名中的a,b分别代表 ASCII 和 binary string。谈到这两个函数就不得不提到base64。Base64就是一种基于64个可打印字符来表示二进制数据的方法.Base64 encode得到的字符串是ASCII码的子集。那么什么又是binary string呢? binary string设计的目的是用来代表和操作二进制数据,而不是用来编码字符串的。
为什么中文不能使用atob,btoa函数?因为binary string的范围是0-255,中文utf-8已经超过这个范围了。这篇文章讲解了字符编码(ASCII LATIN1 UTF8)相关知识。
base64编码规则
参考: 廖雪峰
以对'A'base64编码为例:
btoa('A') //"QQ=="
根据base64的原理,我们试着自己实现一下:
//1. charcode
'A'.charCodeAt().toString(2) //"1000001"
//7位前面补成8位 加一个0
"1000001".padStart(8, '0') //"01000001"
//3. 为了达到24位的整数倍,补两个0x00
"01000001" + '00'.repeat(8) //"010000010000000000000000"
//4. 按6位一组分开
["010000", "010000", "000000", "000000"]=> [16,16,0,0] => [Q,Q,A,A]
//查表得到字符串,两个==表示补了两字节0x00,也取代了原来的A的作用,补了0x00之后,生成的base64字符串末尾肯定是0
`QQ==`
综上所述,经过base64编码得到的字符串长度一定是4的倍数。末尾可能有0,1,2个等号,用来表示在编码时补位的个数。
接下来,我们尝试写一个hex2base64的函数,假设有一个sha512是:
feb3b01a22d0e31a89a50b5ddf20cda0f33e4288fd181115e1bf983b1ecfeb34a5d1ee25a7612af03c30496dab0ccd3927425c17946bfd53357c9d31c8e745ff,我们看下如何把它转成base64:
function hex2base64(hex){
if (typeof hex !== 'string') {
throw new Error('hex should be string');
}
if (!hex.match(/^0x/)) {
hex = '0x' + hex;
}
hex = BigInt(hex) << BigInt(hex.length * 4 % 24);
const b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
const hexStr = hex.toString(2);
console.log(hexStr);
let res = '';
for (let i = 0; i < hexStr.length; i+=6) {
const idx = parseInt(hexStr.slice(i, i+6), 2);
res += b64[idx];
}
res = res.replace(/(A+)$/, (all, match) => '='.repeat(match.length))
return res;
}
使用node.js就不用这么麻烦:
const buffer = Buffer.from(hexStr, 'hex');
console.log(buffer.toString('base64'));
JS字符集
'😂'.length
// 2
'😂'.charCodeAt().toString(16)
//"d83d"
'😂'.charCodeAt(1).toString(16)
//"de02"
"\ud83d\ude02"
//"😂"
encodeURIComponent('中')为什么得到 "%E4%B8%AD"
首先我们要知道utf-8的补位,接下来会分以下几步
'中'.charCodeAt(0) // 20013
(20013).toString(2) // 得到"100111000101101" 15位,
//1 先高位补一个0凑成16位"0100111000101101",
//2 再按UTF-8编码规则,1110 {4} 10 {6} 10 {6}得到24位 111001001011100010101101
//3 再拆分成8位一组。[11100100,10111000,10101101]
//4 再2进制转换成16进制["e4", "b8", "ad"]
//5 最后toUpperCase,%连接
File
在浏览器中,我们可以通过File api操作文件,我们可以通过input元素拿到file,也可以直接调用构造函数创建一个file实例
// 从input元素中读取一个文件:
let fileInput = document.getElementById('file')
fileInput.onchange = console.log(fileInput.files[0])
// 直接创建一个
let file = new File(['1'], '1.txt')
file instanceof File // true
file instanceof Blob // true
file和其他类型之间的转换是一个异步的过程,是通过fileReader来实现的,转换的结果在reader的onload事件中获取,代码如下:
- file to base64
dataUrl除去MIME信息以外才是base64的数据
let reader = new FileReader();
reader.onload = event => console.log(event.target.result);
reader.readAsDataURL(file);
- file to arrayBuffer
let reader = new FileReader(file);
reader.onload = event => console.log(event.target.result)
reader.readAsArrayBuffer(file);
- file to binaryString
reader.readAsBinaryString(file)
- base64 to file
function dataURLtoFile(dataurl, filename) {
let arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type:mime});
}
MIME
媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型 )是一种标准,用来表示文档、文件或字节流的性质和格式。
所有的MIME类型可以在这里找到:https://www.iana.org/assignments/media-types/media-types.xhtml
再提一下文件编码格式,参考文章,使用hexdump
命令可以查看文件的二进制编码。
canvas.toBlob canvas.toDataURL
// 异步,blob会被传到callback的参数中
canvas.toBlob(callback, mimeType, qualityArgument);
//同步
canvas.toDataURL(type, encoderOptions);
window.URL.createObjectURL
objectURL = URL.createObjectURL(blob);
//"blob:https://i.cnblogs.com/64556585-a84a-450c-b7a3-bd54a51b5fdd"
爱奇艺的视频地址使用这种方式
blob和arrayBuffer
blob和arraybuffer的区别可以看这里
将blob转成arrayBuffer: blob => FileReader.readAsArrayBuffer => arrayBuffer
直接创建个arrayBuffer
new ArrayBuffer(length)
TypedArray(uint8Array...)
typedArray不是一个api,而是一类api的统称,它们被设计用来操作arrayBuffer,但是不能直接接收blob,构造函数如下:
new TypedArray(length);
new TypedArray(typedArray);
new TypedArray(object);
new TypedArray(buffer [, byteOffset [, length]]);
TypedArray和arrayBuffer的关系
typedArray是操作arrayBuffer的方式
TypedArray之间的区别
元素的容量不同,详见:MDN
比如:Int8Array元素大小1字节,Int16Array每个元素占2字节
web和二进制数据
xhr2, fetch,Response,Request,Body
res.json() res.text() res.arrayBuffer()
当后端以流的形式返回一个图片时:
fetch(imgurl).then(res => {
return res.blob()
}).then(blob => {
if (blob) {
// 以objectUrl形式显示
img.src = URL.createObjectURL(blob);
// 以dataUrl形式显示图片
let reader = new FileReader(blob);
reader.onload = event => {
img.src = event.target.result;
}
reader.readAsDataURL(blob);
}
向worker传递二进制数据
直接传递即可
参考:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Typed_arrays