一、 ArrayBuffer
ArrayBuffer
對象、TypedArray
視圖和DataView
視圖是 JavaScript 操作二進制數據的一個接口。這些對象早就存在,屬於獨立的規格(2011 年 2 月發布),ES6 將它們納入了 ECMAScript 規格,並且增加了新的方法。它們都是以數組的語法處理二進制數據,所以統稱為二進制數組。
二進制數組由三類對象組成。
(1)ArrayBuffer
對象:代表內存之中的一段二進制數據,可以通過“視圖”進行操作。“視圖”部署了數組接口,這意味着,可以用數組的方法操作內存。
(2)TypedArray
視圖:共包括 9 種類型的視圖,比如Uint8Array
(無符號 8 位整數)數組視圖, Int16Array
(16 位整數)數組視圖, Float32Array
(32 位浮點數)數組視圖等等。
(3)DataView
視圖:可以自定義復合格式的視圖,比如第一個字節是 Uint8(無符號 8 位整數)、第二、三個字節是 Int16(16 位整數)、第四個字節開始是 Float32(32 位浮點數)等等,此外還可以自定義字節序。
簡單說,ArrayBuffer
對象代表原始的二進制數據,TypedArray
視圖用來讀寫簡單類型的二進制數據,DataView
視圖用來讀寫復雜類型的二進制數據。
TypedArray
視圖支持的數據類型一共有 9 種(DataView
視圖支持除Uint8C
以外的其他 8 種)。
數據類型 | 字節長度 | 含義 | 對應的 C 語言類型 |
---|---|---|---|
Int8 | 1 | 8 位帶符號整數 | signed char |
Uint8 | 1 | 8 位不帶符號整數 | unsigned char |
Uint8C | 1 | 8 位不帶符號整數(自動過濾溢出) | unsigned char |
Int16 | 2 | 16 位帶符號整數 | short |
Uint16 | 2 | 16 位不帶符號整數 | unsigned short |
Int32 | 4 | 32 位帶符號整數 | int |
Uint32 | 4 | 32 位不帶符號的整數 | unsigned int |
Float32 | 4 | 32 位浮點數 | float |
Float64 | 8 | 64 位浮點數 | double |
注意,二進制數組並不是真正的數組,而是類似數組的對象。
很多瀏覽器操作的 API,用到了二進制數組操作二進制數據,下面是其中的幾個。
ArrayBuffer 對象概述
ArrayBuffer
對象代表儲存二進制數據的一段內存,它不能直接讀寫,只能通過視圖(TypedArray
視圖和DataView
視圖)來讀寫,視圖的作用是以指定格式解讀二進制數據。
ArrayBuffer
也是一個構造函數,可以分配一段可以存放數據的連續內存區域。
const buf = new ArrayBuffer(32);
上面代碼生成了一段 32 字節的內存區域,每個字節的值默認都是 0。可以看到,ArrayBuffer
構造函數的參數是所需要的內存大小(單位字節)。
為了讀寫這段內容,需要為它指定視圖。DataView
視圖的創建,需要提供ArrayBuffer
對象實例作為參數。
const buf = new ArrayBuffer(32); const dataView = new DataView(buf); dataView.getUint8(0) // 0
上面代碼對一段 32 字節的內存,建立DataView
視圖,然后以不帶符號的 8 位整數格式,從頭讀取 8 位二進制數據,結果得到 0,因為原始內存的ArrayBuffer
對象,默認所有位都是 0。
另一種TypedArray
視圖,與DataView
視圖的一個區別是,它不是一個構造函數,而是一組構造函數,代表不同的數據格式。
const buffer = new ArrayBuffer(12); const x1 = new Int32Array(buffer); x1[0] = 1; const x2 = new Uint8Array(buffer); x2[0] = 2; x1[0] // 2
上面代碼對同一段內存,分別建立兩種視圖:32 位帶符號整數(Int32Array
構造函數)和 8 位不帶符號整數(Uint8Array
構造函數)。由於兩個視圖對應的是同一段內存,一個視圖修改底層內存,會影響到另一個視圖。
TypedArray
視圖的構造函數,除了接受ArrayBuffer
實例作為參數,還可以接受普通數組作為參數,直接分配內存生成底層的ArrayBuffer
實例,並同時完成對這段內存的賦值。
const typedArray = new Uint8Array([0,1,2]); typedArray.length // 3 typedArray[0] = 5; typedArray // [5, 1, 2]
上面代碼使用TypedArray
視圖的Uint8Array
構造函數,新建一個不帶符號的 8 位整數視圖。可以看到,Uint8Array
直接使用普通數組作為參數,對底層內存的賦值同時完成。
ArrayBuffer.prototype.byteLength :返回所分配的內存區域的字節長度
ArrayBuffer.prototype.slice() :允許將內存區域的一部分,拷貝生成一個新的ArrayBuffer
對象。
ArrayBuffer.isView()
ArrayBuffer
有一個靜態方法isView
,返回一個布爾值,表示參數是否為ArrayBuffer
的視圖實例。這個方法大致相當於判斷參數,是否為TypedArray
實例或DataView
實例。
const buffer = new ArrayBuffer(8); ArrayBuffer.isView(buffer) // false const v = new Int32Array(buffer); ArrayBuffer.isView(v) // true
二、TypedArray 視圖
ArrayBuffer
對象作為內存區域,可以存放多種類型的數據。同一段內存,不同數據有不同的解讀方式,這就叫做“視圖”(view)。ArrayBuffer
有兩種視圖,一種是TypedArray
視圖,另一種是DataView
視圖。前者的數組成員都是同一個數據類型,后者的數組成員可以是不同的數據類型。
目前,TypedArray
視圖一共包括 9 種類型,每一種視圖都是一種構造函數。
Int8Array
:8 位有符號整數,長度 1 個字節。Uint8Array
:8 位無符號整數,長度 1 個字節。Uint8ClampedArray
:8 位無符號整數,長度 1 個字節,溢出處理不同。Int16Array
:16 位有符號整數,長度 2 個字節。Uint16Array
:16 位無符號整數,長度 2 個字節。Int32Array
:32 位有符號整數,長度 4 個字節。Uint32Array
:32 位無符號整數,長度 4 個字節。Float32Array
:32 位浮點數,長度 4 個字節。Float64Array
:64 位浮點數,長度 8 個字節。
這 9 個構造函數生成的數組,統稱為TypedArray
視圖。它們很像普通數組,都有length
屬性,都能用方括號運算符([]
)獲取單個元素,所有數組的方法,在它們上面都能使用。普通數組與 TypedArray 數組的差異主要在以下方面。
- TypedArray 數組的所有成員,都是同一種類型。
- TypedArray 數組的成員是連續的,不會有空位。
- TypedArray 數組成員的默認值為 0。比如,
new Array(10)
返回一個普通數組,里面沒有任何成員,只是 10 個空位;new Uint8Array(10)
返回一個 TypedArray 數組,里面 10 個成員都是 0。 - TypedArray 數組只是一層視圖,本身不儲存數據,它的數據都儲存在底層的
ArrayBuffer
對象之中,要獲取底層對象必須使用buffer
屬性。
// 創建一個8字節的ArrayBuffer const b = new ArrayBuffer(8); // 創建一個指向b的Int32視圖,開始於字節0,直到緩沖區的末尾 const v1 = new Int32Array(b); // 創建一個指向b的Uint8視圖,開始於字節2,直到緩沖區的末尾 const v2 = new Uint8Array(b, 2); // 創建一個指向b的Int16視圖,開始於字節2,長度為2 const v3 = new Int16Array(b, 2, 2);
TypedArray.prototype.copyWithin(target, start[, end = this.length])
TypedArray.prototype.entries()
TypedArray.prototype.every(callbackfn, thisArg?)
TypedArray.prototype.fill(value, start=0, end=this.length)
TypedArray.prototype.filter(callbackfn, thisArg?)
TypedArray.prototype.find(predicate, thisArg?)
TypedArray.prototype.findIndex(predicate, thisArg?)
TypedArray.prototype.forEach(callbackfn, thisArg?)
TypedArray.prototype.indexOf(searchElement, fromIndex=0)
TypedArray.prototype.join(separator)
TypedArray.prototype.keys()
TypedArray.prototype.lastIndexOf(searchElement, fromIndex?)
TypedArray.prototype.map(callbackfn, thisArg?)
TypedArray.prototype.reduce(callbackfn, initialValue?)
TypedArray.prototype.reduceRight(callbackfn, initialValue?)
TypedArray.prototype.reverse()
TypedArray.prototype.slice(start=0, end=this.length)
TypedArray.prototype.some(callbackfn, thisArg?)
TypedArray.prototype.sort(comparefn)
TypedArray.prototype.toLocaleString(reserved1?, reserved2?)
TypedArray.prototype.toString()
TypedArray.prototype.values()
BYTES_PER_ELEMENT 屬性
每一種視圖的構造函數,都有一個BYTES_PER_ELEMENT
屬性,表示這種數據類型占據的字節數。
Int8Array.BYTES_PER_ELEMENT // 1 Uint8Array.BYTES_PER_ELEMENT // 1 Uint8ClampedArray.BYTES_PER_ELEMENT // 1 Int16Array.BYTES_PER_ELEMENT // 2 Uint16Array.BYTES_PER_ELEMENT // 2 Int32Array.BYTES_PER_ELEMENT // 4 Uint32Array.BYTES_PER_ELEMENT // 4 Float32Array.BYTES_PER_ELEMENT // 4 Float64Array.BYTES_PER_ELEMENT // 8
三、DataView 視圖
如果一段數據包括多種類型(比如服務器傳來的 HTTP 數據),這時除了建立ArrayBuffer
對象的復合視圖以外,還可以通過DataView
視圖進行操作。
DataView
視圖提供更多操作選項,而且支持設定字節序。本來,在設計目的上,ArrayBuffer
對象的各種TypedArray
視圖,是用來向網卡、聲卡之類的本機設備傳送數據,所以使用本機的字節序就可以了;而DataView
視圖的設計目的,是用來處理網絡設備傳來的數據,所以大端字節序或小端字節序是可以自行設定的。
DataView
視圖本身也是構造函數,接受一個ArrayBuffer
對象作為參數,生成視圖。
new DataView(ArrayBuffer buffer [, 字節起始位置 [, 長度]]);
下面是一個例子。
const buffer = new ArrayBuffer(24); const dv = new DataView(buffer);
DataView
實例有以下屬性,含義與TypedArray
實例的同名方法相同。
DataView.prototype.buffer
:返回對應的 ArrayBuffer 對象DataView.prototype.byteLength
:返回占據的內存字節長度DataView.prototype.byteOffset
:返回當前視圖從對應的 ArrayBuffer 對象的哪個字節開始
DataView
實例提供 8 個方法讀取內存。
getInt8
:讀取 1 個字節,返回一個 8 位整數。getUint8
:讀取 1 個字節,返回一個無符號的 8 位整數。getInt16
:讀取 2 個字節,返回一個 16 位整數。getUint16
:讀取 2 個字節,返回一個無符號的 16 位整數。getInt32
:讀取 4 個字節,返回一個 32 位整數。getUint32
:讀取 4 個字節,返回一個無符號的 32 位整數。getFloat32
:讀取 4 個字節,返回一個 32 位浮點數。getFloat64
:讀取 8 個字節,返回一個 64 位浮點數。
這一系列get
方法的參數都是一個字節序號(不能是負數,否則會報錯),表示從哪個字節開始讀取。
const buffer = new ArrayBuffer(24); const dv = new DataView(buffer); // 從第1個字節讀取一個8位無符號整數 const v1 = dv.getUint8(0); // 從第2個字節讀取一個16位無符號整數 const v2 = dv.getUint16(1); // 從第4個字節讀取一個16位無符號整數 const v3 = dv.getUint16(3);
上面代碼讀取了ArrayBuffer
對象的前 5 個字節,其中有一個 8 位整數和兩個十六位整數。
如果一次讀取兩個或兩個以上字節,就必須明確數據的存儲方式,到底是小端字節序還是大端字節序。默認情況下,DataView
的get
方法使用大端字節序解讀數據,如果需要使用小端字節序解讀,必須在get
方法的第二個參數指定true
。
// 小端字節序 const v1 = dv.getUint16(1, true); // 大端字節序 const v2 = dv.getUint16(3, false); // 大端字節序 const v3 = dv.getUint16(3);
DataView 視圖提供 8 個方法寫入內存。
setInt8
:寫入 1 個字節的 8 位整數。setUint8
:寫入 1 個字節的 8 位無符號整數。setInt16
:寫入 2 個字節的 16 位整數。setUint16
:寫入 2 個字節的 16 位無符號整數。setInt32
:寫入 4 個字節的 32 位整數。setUint32
:寫入 4 個字節的 32 位無符號整數。setFloat32
:寫入 4 個字節的 32 位浮點數。setFloat64
:寫入 8 個字節的 64 位浮點數。
這一系列set
方法,接受兩個參數,第一個參數是字節序號,表示從哪個字節開始寫入,第二個參數為寫入的數據。對於那些寫入兩個或兩個以上字節的方法,需要指定第三個參數,false
或者undefined
表示使用大端字節序寫入,true
表示使用小端字節序寫入。
// 在第1個字節,以大端字節序寫入值為25的32位整數 dv.setInt32(0, 25, false); // 在第5個字節,以大端字節序寫入值為25的32位整數 dv.setInt32(4, 25); // 在第9個字節,以小端字節序寫入值為2.5的32位浮點數 dv.setFloat32(8, 2.5, true);
如果不確定正在使用的計算機的字節序,可以采用下面的判斷方式。
const littleEndian = (function() { const buffer = new ArrayBuffer(2); new DataView(buffer).setInt16(0, 256, true); return new Int16Array(buffer)[0] === 256; })();
如果返回true
,就是小端字節序;如果返回false
,就是大端字節序。