ArrayBuffer 的應用場景
1.AJAX
傳統上,服務器通過 AJAX 操作只能返回文本數據,即responseType
屬性默認為text
。XMLHttpRequest
第二版XHR2
允許服務器返回二進制數據,這時分成兩種情況。如果明確知道返回的二進制數據類型,可以把返回類型(responseType
)設為arraybuffer
;如果不知道,就設為blob
。
let xhr = new XMLHttpRequest(); xhr.open('GET', someUrl); xhr.responseType = 'arraybuffer'; xhr.onload = function () { let arrayBuffer = xhr.response; // ··· }; xhr.send();
如果知道傳回來的是 32 位整數,可以像下面這樣處理。
xhr.onreadystatechange = function () { if (req.readyState === 4 ) { const arrayResponse = xhr.response; const dataView = new DataView(arrayResponse); const ints = new Uint32Array(dataView.byteLength / 4); xhrDiv.style.backgroundColor = "#00FF00"; xhrDiv.innerText = "Array is " + ints.length + "uints long"; } }
2.Canvas
網頁Canvas
元素輸出的二進制像素數據,就是 TypedArray 數組。
const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const uint8ClampedArray = imageData.data;
需要注意的是,上面代碼的uint8ClampedArray
雖然是一個 TypedArray 數組,但是它的視圖類型是一種針對Canvas
元素的專有類型Uint8ClampedArray
。這個視圖類型的特點,就是專門針對顏色,把每個字節解讀為無符號的 8 位整數,即只能取值 0 ~ 255,而且發生運算的時候自動過濾高位溢出。這為圖像處理帶來了巨大的方便。
舉例來說,如果把像素的顏色值設為Uint8Array
類型,那么乘以一個 gamma 值的時候,就必須這樣計算:
u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));
因為Uint8Array
類型對於大於 255 的運算結果(比如0xFF+1
),會自動變為0x00
,所以圖像處理必須要像上面這樣算。這樣做很麻煩,而且影響性能。如果將顏色值設為Uint8ClampedArray
類型,計算就簡化許多。
pixels[i] *= gamma;
Uint8ClampedArray
類型確保將小於 0 的值設為 0,將大於 255 的值設為 255。注意,IE 10 不支持該類型。
3.WebSocket
WebSocket
可以通過ArrayBuffer
,發送或接收二進制數據。
let socket = new WebSocket('ws://127.0.0.1:8081'); socket.binaryType = 'arraybuffer'; // Wait until socket is open socket.addEventListener('open', function (event) { // Send binary data const typedArray = new Uint8Array(4); socket.send(typedArray.buffer); }); // Receive binary data socket.addEventListener('message', function (event) { const arrayBuffer = event.data; // ··· });
4.Fetch API
Fetch API 取回的數據,就是ArrayBuffer
對象。
fetch(url) .then(function(response){ return response.arrayBuffer() }) .then(function(arrayBuffer){ // ... });
5.File API
如果知道一個文件的二進制數據類型,也可以將這個文件讀取為ArrayBuffer
對象。
const fileInput = document.getElementById('fileInput'); const file = fileInput.files[0]; const reader = new FileReader(); reader.readAsArrayBuffer(file); reader.onload = function () { const arrayBuffer = reader.result; // ··· };
下面以處理 bmp 文件為例。假定file
變量是一個指向 bmp 文件的文件對象,首先讀取文件。
const reader = new FileReader(); reader.addEventListener("load", processimage, false); reader.readAsArrayBuffer(file);
然后,定義處理圖像的回調函數:先在二進制數據之上建立一個DataView
視圖,再建立一個bitmap
對象,用於存放處理后的數據,最后將圖像展示在Canvas
元素之中。
function processimage(e) { const buffer = e.target.result; const datav = new DataView(buffer); const bitmap = {}; // 具體的處理步驟 }
具體處理圖像數據時,先處理 bmp 的文件頭。具體每個文件頭的格式和定義,請參閱有關資料。
bitmap.fileheader = {}; bitmap.fileheader.bfType = datav.getUint16(0, true); bitmap.fileheader.bfSize = datav.getUint32(2, true); bitmap.fileheader.bfReserved1 = datav.getUint16(6, true); bitmap.fileheader.bfReserved2 = datav.getUint16(8, true); bitmap.fileheader.bfOffBits = datav.getUint32(10, true);
接着處理圖像元信息部分。
bitmap.infoheader = {}; bitmap.infoheader.biSize = datav.getUint32(14, true); bitmap.infoheader.biWidth = datav.getUint32(18, true); bitmap.infoheader.biHeight = datav.getUint32(22, true); bitmap.infoheader.biPlanes = datav.getUint16(26, true); bitmap.infoheader.biBitCount = datav.getUint16(28, true); bitmap.infoheader.biCompression = datav.getUint32(30, true); bitmap.infoheader.biSizeImage = datav.getUint32(34, true); bitmap.infoheader.biXPelsPerMeter = datav.getUint32(38, true); bitmap.infoheader.biYPelsPerMeter = datav.getUint32(42, true); bitmap.infoheader.biClrUsed = datav.getUint32(46, true); bitmap.infoheader.biClrImportant = datav.getUint32(50, true);
最后處理圖像本身的像素信息。
const start = bitmap.fileheader.bfOffBits; bitmap.pixels = new Uint8Array(buffer, start);
至此,圖像文件的數據全部處理完成。下一步,可以根據需要,進行圖像變形,或者轉換格式,或者展示在Canvas
網頁元素之中。
6.SharedArrayBuffer
JavaScript 是單線程的,Web worker 引入了多線程:主線程用來與用戶互動,Worker 線程用來承擔計算任務。每個線程的數據都是隔離的,通過postMessage()
通信。下面是一個例子。
// 主線程 const w = new Worker('myworker.js');
上面代碼中,主線程新建了一個 Worker 線程。該線程與主線程之間會有一個通信渠道,主線程通過w.postMessage
向 Worker 線程發消息,同時通過message
事件監聽 Worker 線程的回應。
// 主線程 w.postMessage('hi'); w.onmessage = function (ev) { console.log(ev.data); }
上面代碼中,主線程先發一個消息hi
,然后在監聽到 Worker 線程的回應后,就將其打印出來。
Worker 線程也是通過監聽message
事件,來獲取主線程發來的消息,並作出反應。
// Worker 線程 onmessage = function (ev) { console.log(ev.data); postMessage('ho'); }
線程之間的數據交換可以是各種格式,不僅僅是字符串,也可以是二進制數據。這種交換采用的是復制機制,即一個進程將需要分享的數據復制一份,通過postMessage
方法交給另一個進程。如果數據量比較大,這種通信的效率顯然比較低。很容易想到,這時可以留出一塊內存區域,由主線程與 Worker 線程共享,兩方都可以讀寫,那么就會大大提高效率,協作起來也會比較簡單(不像postMessage
那么麻煩)。
ES2017 引入SharedArrayBuffer
,允許 Worker 線程與主線程共享同一塊內存。SharedArrayBuffer
的 API 與ArrayBuffer
一模一樣,唯一的區別是后者無法共享數據。
// 主線程 // 新建 1KB 共享內存 const sharedBuffer = new SharedArrayBuffer(1024); // 主線程將共享內存的地址發送出去 w.postMessage(sharedBuffer); // 在共享內存上建立視圖,供寫入數據 const sharedArray = new Int32Array(sharedBuffer);
上面代碼中,postMessage
方法的參數是SharedArrayBuffer
對象。
Worker 線程從事件的data
屬性上面取到數據。
// Worker 線程 onmessage = function (ev) { // 主線程共享的數據,就是 1KB 的共享內存 const sharedBuffer = ev.data; // 在共享內存上建立視圖,方便讀寫 const sharedArray = new Int32Array(sharedBuffer); // ... };
共享內存也可以在 Worker 線程創建,發給主線程。
SharedArrayBuffer
與ArrayBuffer
一樣,本身是無法讀寫的,必須在上面建立視圖,然后通過視圖讀寫。
// 分配 10 萬個 32 位整數占據的內存空間 const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 100000); // 建立 32 位整數視圖 const ia = new Int32Array(sab); // ia.length == 100000 // 新建一個質數生成器 const primes = new PrimeGenerator(); // 將 10 萬個質數,寫入這段內存空間 for ( let i=0 ; i < ia.length ; i++ ) ia[i] = primes.next(); // 向 Worker 線程發送這段共享內存 w.postMessage(ia);
Worker 線程收到數據后的處理如下。
// Worker 線程 let ia; onmessage = function (ev) { ia = ev.data; console.log(ia.length); // 100000 console.log(ia[37]); // 輸出 163,因為這是第38個質數 };
7.Atomics 對象
多線程共享內存,最大的問題就是如何防止兩個線程同時修改某個地址,或者說,當一個線程修改共享內存以后,必須有一個機制讓其他線程同步。SharedArrayBuffer API 提供Atomics
對象,保證所有共享內存的操作都是“原子性”的,並且可以在所有線程內同步。
此處不錯更多探討.........
更多: