一直以來,前端的工作主要涉及的是字符串操作,而對二進制的數據接觸較少。但是這種需求卻一直存在着,尤其是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個等號,用來表示在編碼時補位的個數。
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