一、Uint8Array 介紹
Uint8Array 數組類型表示一個8位無符號整型數組,創建時內容被初始化為0。創建完后,可以以對象的方式或使用數組下標索引的方式引用數組中的元素。
詳細介紹見 MDN 描述:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
我們要使用了解的話,主要看下示例代碼:
// 來自長度
var uint8 = new Uint8Array(2); uint8[0] = 42; console.log(uint8[0]); // 42
console.log(uint8.length); // 2
console.log(uint8.BYTES_PER_ELEMENT); // 1 // 來自數組
var arr = new Uint8Array([21,31]); console.log(arr[1]); // 31 // 來自另一個 TypedArray
var x = new Uint8Array([21, 31]); var y = new Uint8Array(x); console.log(y[0]); // 21 // 來自 ArrayBuffer
var buffer = new ArrayBuffer(8); var z = new Uint8Array(buffer, 1, 4);
z; // Uint8Array(4) [0, 0, 0, 0] // 來自一個迭代器
var iterable = function*(){ yield* [1,2,3]; }(); var uint8 = new Uint8Array(iterable); // Uint8Array[1, 2, 3]
二、Uint8Array.slice 與 Uint8Array.subarray 區別
最近在處理 png 圖像解碼時,使用到Uint8Array對象。發現該對象在部分android瀏覽器中沒有slice方法。翻了一遍API文檔,對象的slice與subarray方法字面描述基本一樣。於是使用subarray方法取而代之。結果自然是很悲催了,解析出每幀數據都一樣(解碼過程中有去修改對象數據)。
到這里大概已經猜到了subarray與slice的區別就在於內存空間占用上。為探個究竟,跑個簡單的demo來驗證下:
let a = new Uint8Array([1,2,3,4,5,6]); let b = a.subarray(3,5); let c = a.slice(3,5); // 將b的第一個值改為9
b[0] = 9; console.log('a',a); // 輸出:a Uint8Array(6) [1, 2, 3, 9, 5, 6] console.log('b',b); // 輸出:b Uint8Array(2) [9, 5] console.log('c',c); // 輸出:c Uint8Array(2) [4, 5]
果然,修改b,a對應的值也是隨之變化的,說明是在同一內存空間上。而c不與前者內存共享,是在獨立的空間上。
問題是找到了,解決辦法就自然簡單了。方法有無數種。檢查原型是否有對應的方法肯定是必不可少的。如果原型上沒有slice就自行往原型上添加一個即可。循環效率較低,這里還是決定使用原型本身的subarray來處理。最后解決問題的兼容代碼如下:
// 兼容代碼,如果原型上無`slice`則添加一個
if(!Uint8Array.prototype.slice){ Uint8Array.prototype.slice = function(...arg){ return new Uint8Array(this).subarray(...arg); } }; let a = new Uint8Array([1,2,3,4,5,6]); let b = b.slice(3,5); console.log('a',a); // 輸出:a Uint8Array(6) [1, 2, 3, 4, 5, 6]
console.log('b',b); // 輸出:b Uint8Array(2) [4, 5]
這里其實也比較簡單,就是新建了一個 Unit8Array 然后去 subarray()。
三、Uint8Array 構造函數對 typedArray 的引用問題
Javascript 的 Uint8Array 支持字節數據,對於操作二進制數據非常有用,筆者初次接觸時發現它有幾個構造函數,如下:
new Uint8Array(); new Uint8Array(length); new Uint8Array(typedArray); new Uint8Array(object); new Uint8Array(buffer [, byteOffset [, length]]);
這些函數都返回一個 Uint8Array 類型的對象,但對於 new Uint8Array(typedArray); 這個形式的構造函數需要理解一下。在MDN的官方文檔里描述不詳:
new Uint8Array(typedArray) 表示根據 typedArray 提供的對象創建一個 Uint8Array 對象,並保持對typedArray對象的引用,這個形式的構造函數不會復制typedArray對象的。嘗試以下代碼,看看輸出結果。
// create a TypedArray with a size in bytes
var buffer = new ArrayBuffer(8); var typedArray1 = new Uint8Array(buffer); typedArray1[0] = 32; typedArray1[1] = 33; typedArray1[2] = 34; var typedArray2 = new Uint8Array(buffer); typedArray2[0] = 42; typedArray2[1] = 43; typedArray2[2] = 44; console.log(typedArray1); //不是輸出 Uint8Array [32, 33, 34, 0, 0, 0, 0, 0] //正確輸出 Uint8Array [42, 43, 44, 0, 0, 0, 0, 0]
console.log(typedArray2); //正確輸出 Uint8Array [42, 43, 44, 0, 0, 0, 0, 0]
從以上代碼看到了 typedArray1 與 typedArray2 輸出完全一樣。原因就是 typedArray1.buffer 與 typedArray2.buffer 指向的是同一個對象,因此分別修改 typedArray1 與 typedArray2 時,實際上修改的是同一內存對象。
When creating an instance of a TypedArray (e.g. Int8Array), an array buffer is created internally in memory or, if an ArrayBuffer object is given as constructor argument, then this is used instead. The buffer address is saved as an internal property of the instance and all the methods of %TypedArray%.prototype, i.e. set value and get value etc., operate on that array buffer address.
因此,需要注意的是:用 ArrayBuffer 作為構造函數的參數時,Uint8Array直接引用這個ArrayBuffer對象作為內部緩沖,而不再創建內部ArrayBuffer對象。
四、Uint8Array 與 String 互轉
1、字符串轉Uint8Array
function stringToUint8Array(str){ var arr = []; for (var i = 0, j = str.length; i < j; ++i) { arr.push(str.charCodeAt(i)); } var tmpUint8Array = new Uint8Array(arr); return tmpUint8Array }
2、Uint8Array轉字符串
function Uint8ArrayToString(fileData){ var dataString = ""; for (var i = 0; i < fileData.length; i++) { dataString += String.fromCharCode(fileData[i]); } return dataString }
