原文:Javascript中的二進制數據類型javascript
最近工做上接手的一個項目涉及到了圖片的加密壓縮與上傳,也就須要用JavaScript直接去操做二進制數據,特意整理了一下JavaScript中的二進制數據類型,當作學習筆記。html
歷史上JavaScript是沒有讀寫二進制數據能力的,但隨着es5中Blob對象的引入以及es6中ArrayBuffer對象、TypedArray和DataView對象的規範化,js處理二進制數據的能力大幅度加強,也能直接處理文件流,網絡流等二進制Buffer數據了。java
ArrayBuffer表明內存當中的一段通用的、固定長度的原始二進制數據緩衝區。它沒法被直接讀寫,須要配合TypedArray對象或DataView對象來操做緩衝區的內容。es6
咱們能夠直接經過ArrayBuffer構造函數來建立一段存放二進制數據的連續內存區域。web
const buffer = new ArrayBuffer(8);
複製代碼
這裏咱們建立了一個長度爲8個字節的連續內存區域。因爲咱們沒有賦初值,每個字節單元的值都默認是0.(這裏注意ArrayBuffer不能像數組那樣直接經過索引下標來訪問內存單元的數據)json
ArrayBuffer有一個只讀的byteLength
屬性能夠讀取數組的字節長度,這在ArrayBuffer建立的時候就已經肯定。ArrayBuffer還有一個ArrayBuffer.prototype.slice
方法用來拷貝自身的一部分生成一個新的ArrayBuffer。canvas
const buffer = new ArrayBuffer(8);
buffer.byteLength // 8
const buf = buffer.slice(0, 4);
buf.byteLength // 4
複製代碼
但ArrayBuffer自身是不可直接讀寫的,更多操做仍是須要依靠TypedArray或DataView來實現。api
咱們經過ArrayBuffer在內存中存儲一段未加工的二進制數據,這一段二進制數據自己只是內存中存儲的0,1
數據,咱們經過不一樣的格式來解讀就會賦予它不一樣的意義,這裏的格式也就是‘視圖’,視圖有兩種,一種是TypedArray,一種是DataView。數組
TypedArray是一簇api類似的Array-like對象,如今包括如下九種:瀏覽器
- Int8Array();
- Uint8Array();
- Uint8ClampedArray();
- Int16Array();
- Uint16Array();
- Int32Array();
- Uint32Array();
- Float32Array();
- Float64Array();
生成視圖能夠採用多種方法來。
// TypedArray 不是實際存在的對象,指代以上九種構造函數之一
new TypedArray(length);
new TypedArray(typedArray);
new TypedArray(object);
new TypedArray(buffer [, byteOffset [, length]]);
複製代碼
以Int8Array爲例,咱們建立一個以8bit爲單位存儲單元的內存數組。
// 在已存在的ArrayBuffer 上生成視圖
const buffer = new ArrayBuffer(4);
const x1 = new Int8Array(buffer);
x1 // [0, 0, 0, 0]
// 不借助事先生成的ArrayBuffer, 直接生成視圖
const x2 = new Int8Array(4);
x2[0] = 1;
x2 // [1, 0, 0, 0]
// 經過已有的TypedArray轉化而來
const x3 = new Int8Array(4);
x3[0] = 1;
const x4 = new Int8Array(x3);
x4[0] // 1
x4[0] = 2;
x3[0], x4[0] // 1, 2
// 經過對象轉化,相似於Array.from方法
const x5 = new Int8Array([1, 2, 3, 4]);
x5 // [1, 2, 3, 4]
複製代碼
TypedArray表現和Array相似,能夠用方括號運算符([])訪問特定內存單元的數據,也可使用幾乎全部的數組方法,具體能夠參考MDN。
TypedArray還有幾個特有的屬性與方法。
TypedArray.prototype.buffer: 指向咱們操做的TypedArray引用的ArrayBuffer; TypedArray.prototype.byteLength: 返回ArrayBuffer的字節長度; TypedArray.prototype.byteOffset: 讀取ArrayBuffer時的字節偏移量; TypedArray.prototype.set: 複製一個指定數組的元素保存到TypedArray; TypedArray.prototype.subarray: 拷貝TypedArray的一個子數組爲副本;
const buffer = new ArrayBuffer(4);
const int8Arr = new Int8Array(buffer);
int8Arr.buffer === buffer; // true
int8Arr.byteLength // 4
int8Arr.byteOffset // 0
const int8Arr2 = new Int8Array(4);
int8Arr2.set(int8Arr); // 以int8Arr爲數據來源製做了一個相同數據的副本
int8Arr.subarray(2, 3); // 返回以int8Arr中索引從2(含)到3(不含)的數據爲數據來源的副本
複製代碼
不一樣於類型化數組,一個數組只能存放同一類型的數據,DataView能夠在內存中存放不一樣類型的數據。但TypedArray的一系列方法足以應付各類場景下的ArrayBuffer存取操做,須要用到DataView的場景較少,這裏簡單介紹下。
DataView對象接受一個ArrayBuffer生成視圖。
// new DataView(buffer [, byteOffset [, length]]);
const buffer = new ArrayBuffer(4);
const dv = new DataView(buffer);
複製代碼
DataView有十六個實例方法對應於8種數據類型的存取操做。
- getInt8()
- getUint8()
- getInt16()
- getUint16()
- getInt32()
- getUint32()
- getFloat32()
- getFloat64()
- setInt8()
- setUint8()
- setInt16()
- setUint16()
- setInt32()
- setUint32()
- setFloat32()
- setFloat64()
const buffer = new ArrayBuffer(4);
const dv = new DataView(buffer, 0);
dv.setInt8(0, 4);
dv.getInt8(0);
複製代碼
web瀏覽器中常見的二進制對象有Blob和File等。
Blob對象表明一段二進制對象,並提供了一系列對該二進制數據操做的方法。 生成Blob對象可使用Blob構造函數,或者對已有Blob對象使用slice切片生成。
const obj = {hello: 'world'};
const blob = new Blob([JSON.stringify(obj)], {type: 'application/json'});
複製代碼
Blob構造函數接受兩個參數,一個是buffer數據數組,第二個是隻有一個type屬性的options對象,type指定文件的MIME類型。
生成的Blob實例有兩個只讀的屬性。
size 生成二進制數據的大小,單位是字節 type 生成二進制數據的MIME類型
File對象繼承與Blob對象,在Blob的基礎上增長了一些屬性。
File.prototype instanceof Blob // true
複製代碼
最多見的File類型來自type爲file的input元素。
<input type="file" id="uploader" />
<script> const uploader = document.getElementById('uploader'); uploader.addEventListener('change', () => { const file = uploader.files[0]; // file // { // lastModified: xxxxxxxxxxx, // lastModifiedDate: Thu Apr 11 2019 10:52:35 GMT+0800 (CST), // name: "avatar.png", // size: 7040, // type: "image/png" // } }); </script>
複製代碼
在獲取到Blob對象或File對象後,咱們一般還須要藉助FileReader對象將咱們的二進制數據轉換成須要的形式,如轉換成DataURL後用於image, audio, video等標籤的直接引入,或轉化成ArrayBuffer後進一步對咱們的二進制數據進行操做。
FileReader提供瞭如下轉化方法。
- FileReader.prototype.readAsDataURL
- FileReader.prototype.readAsArrayBuffer
- FileReader.prototype.readAsText
- FileReader.prototype.readAsBinaryString
function readBlob(blob, type) {
return new Promise(resolve => {
const reader = new FileReader();
reader.onload = (e) => {
resolve(e.target.result);
};
reader[`readAs${type}`](blob);
});
}
const blob = new Blob('hello, world!'.split(''));
readBlob(blob, 'DataURL').then(console.log);
複製代碼
除了FileReader外,還有些web api能夠輸出咱們的Blob/File數據。
- URL.createURLObject
- Response對象提供的方法
- Response.prototype.arrayBuffer
- Response.prototype.blob
- Response.prototype.json
- Response.prototype.text
- Response.prototype.formData
const str = 'hello, world';
const blob = new Blob(str.split(''));
URL.createURLObject(blob);
new Response(blob).arrayBuffer(buffer => console.log(new Int8Array(buffer));
// Response也能夠直接轉化一個String
new Response(str).arrayBuffer(buffer => console.log(new Int8Array(buffer));
複製代碼
根據以上的內容,咱們已經能夠進行String, Blob/File, ArrayBuffer, TypedArray, DataURL, ObjectURL這些對象之間的相互轉化了。有的轉化操做直接使用現有api便可,有的須要中間狀態過分。如下爲幾種常見的應用到這些轉換的場景。
將json數據下載爲本地文件。
function download (url, name) {
const a = document.createElement('a')
a.download = name
a.rel = 'noopener'
a.href = url
a.dispatchEvent(new MouseEvent('click')) // or a.click()
}
// 要被下載的json數據
const json = {
name: 'coffeeBean'
};
const str = JSON.stringify(json, null, 4);
const dataURL = `data;,${str}`;
const blob = new Blob(str.split(''));
const url = URL.createObjectURL(blob);
download(dataURL, 'demo1.json');
download(url, 'demo2.json');
複製代碼
上傳本地圖片並在網頁中顯示。
<input type="file" id="uploader" />
<script> const uploader = document.getElementById('uploader'); uploader.addEventListener('change', () => { const file = uploader.files[0]; // readBlob函數在以上FileReader小節定義 readBlob(file, 'DataURL').then(result => { const img = new Image(); img.src = result; document.body.append(img); }); }); </script>
複製代碼
利用canvas實現圖片壓縮
function compress(img, mimeType, maxWidth, quality) {
const canvas = document.createElement('canvas');
cosnt ctx = canvas.getContext('2d');
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = Math.round(height *= maxWidth / width);
width = maxWidth;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
return new Promise(resolve => {
canvas.toBlob(blob => {
resolve(blob);
}, mimeType, quality);
});
}
// compress(img, 'image/png', 1080, 0.9)
// 第一個參數爲要壓縮的圖片對象
// 第二個爲圖片的MIME類型
// 第三個爲圖片等比壓縮後的最大寬度
// 第四個參數爲canvas壓縮的質量
// 函數返回壓縮後的圖片Blob,能夠做爲圖片文件上傳到服務器
複製代碼
js中二進制數據類型的應用場景還有不少,但理解了這些二進制對象之間的關係以及原理以後,就可以在各類二進制數據中自由轉換,場景應對起來也就遊刃有餘了。