[TOC]
* * * * *
[原文地址](http://javascript.ruanyifeng.com/stdlib/arraybuffer.html#)
> `ArrayBuffer`對象、T`ypedArray`對象、`DataView`對象是`JavaScript`操作二進制數據的一個接口。這些對象早就存在,屬于獨立的規格,ES6將它們納入了ECMAScript規格,并且增加了新的方法。
> 首先,這個 `ArrayBuffer` 類型化數組,類型化數組是`JavaScript`操作二進制數據的一個接口。最初為了滿足`JavaScript`與顯卡之間大量的、實時的數據交換,它們之間的數據通信必須是二進制的,而不能是傳統的文本格式。`Arraybuffer`就是一片內存空間,不能直接引用里面的數據,用戶只能通過`typedarray`類型和`dataView`類型引用。
- `ArrayBuffer`對象:代表內存之中的一段二進制數據,可以通過“視圖”進行操作。“視圖”部署了數組接口,這意味著,可以用數組的方法操作內存。
- `TypedArray`對象:用來生成內存的視圖,通過9個構造函數,可以生成9種數據格式的視圖,比如`Uint8Array`(無符號8位整數)數組視圖, Int16Array(16位整數)數組視圖, Float32Array(32位浮點數)數組視圖等等。
- `DataView`對象:用來生成內存的視圖,可以自定義格式和字節序,比如第一個字節是`Uint8`(無符號8位整數)、第二個字節是Int16(16位整數)、第三個字節是Float32(32位浮點數)等等。
簡單說,`ArrayBuffer`對象代表原始的二進制數據,`TypedArray`對象代表確定類型的二進制數據,`DataView`對象代表不確定類型的二進制數據。它們支持的數據類型一共有9種(`DataView`對象支持除`Uint8C`以外的其他8種)。
### 一、 `ArrayBuffer`對象
`ArrayBuffer`對象代表儲存二進制數據的一段內存,它不能直接讀寫,只能通過視圖(`T`ypedArray`視圖和`DataView`視圖)來讀寫,視圖的作用是以指定格式解讀二進制數據。
- **創建 `ArrayBuffer`**
```JavaScript
let buf = new ArrayBuffer(32)
// 生成了一個32字節的內存區域,每個字節默認為0
```
- **讀取 `ArrayBuffer`**
讀取`ArrayBuffer` 我們上邊提了有兩種方式:
第一種方式通過 `DataView` 視圖讀取
```JavaScript
let buf = new ArrayBuffer(32)
let dataView = new DataView(buf)
dataView.getUnt8(0)
// 建立DataView視圖,然后以不帶符號的8位整數格式,讀取第一個元素
```
第二種方式通過 `TypeArray` 視圖,與DataView視圖的一個區別是,它不是一個構造函數,而是一組構造函數,代表不同的數據格式。
```JavaScript
let buf = new ArrayBuffer(32)
let int32 = new Int32Array(buf)
let uint8 = new Uint8Array(buf)
console.log(int32[0]) // 0
console.log(uint8[0]) // 0
// 還可以直接修改
int32[0] = 1;
console.log(int32[0]) // 1
console.log(uint8[0]) // 1
```
上面代碼對同一段內存,分別建立兩種視圖:32位帶符號整數(Int32Array構造函數)和8位不帶符號整數(Uint8Array構造函數)。由于兩個視圖對應的是同一段內存,一個視圖修改底層內存,會影響到另一個視圖。
- **`ArrayBuffer`的其它屬性方法**
> `ArrayBuffer.prototype.byteLength` 返回所分配的內存區域的字節長度。
> `ArrayBuffer.prototype.slice(start,len)` 允許將內存區域的一部分,拷貝生成一個新的ArrayBuffer對象,并返回,start 為開始看吧的位置,len為拷貝長度
> `ArrayBuffer.isView()` 返回一個布爾值,表示參數是否為ArrayBuffer的視圖實例(也就是是否為是否為TypedArray實例或DataView實例)
### 二、 `TypedArray對象`
`ArrayBuffer`對象作為內存區域,可以存放多種類型的數據。同一段內存,不同數據有不同的解讀方式,這就叫做“視圖”(view)。`ArrayBuffer`有兩種視圖,一種是`TypedArray`視圖,另一種是`DataView`視圖,兩者的區別主要是字節序,前者的數組成員都是同一個數據類型,后者的數組成員可以是不同的數據類型。
- **`TypedArray對象`提供的視圖**
目前,TypedArray對象一共提供9種類型的視圖,每一種視圖都是一種構造函數。
| 視圖函數 | 說明 |
| --- | --- |
| Int8Array | 8位有符號整數,長度1個字節 |
| Uint8Array | 8位無符號整數,長度1個字節 |
| Uint8ClampedArray | 位無符號整數,長度1個字節,溢出處理不同 |
| Int16Array | 16位有符號整數,長度2個字 |
| Uint16Array | 16位無符號整數,長度2個字節 |
| Int32Array | 32位有符號整數,長度4個字節 |
| Uint32Array | 32位無符號整數,長度4個字節 |
| Float32Array | 32位浮點數,長度4個字節 |
| Float64Array | 64位浮點數,長度8個字節 |
這9個構造函數生成的對象,統稱為`TypedArray`對象。它們很像正常數組,都有length屬性,都能用方括號運算符([])獲取單個元素,所有數組的方法,在類型化數組上面都能使用。兩者的差異主要在以下方面。
> `TypedArray`數組的所有成員,都是同一種類型和格式。
> `TypedArray`數組的成員是連續的,不會有空位。
> `Typed`化數組成員的默認值為0。比如,`new Array(10)`返回一個正常數組,里面沒有任何成員,只是10個空位;`new Uint8Array(10)`返回一個類型化數組,里面10個成員都是0。
> `TypedArray`數組只是一層視圖,本身不儲存數據,它的數據都儲存在底層的`ArrayBuffer`對象之中,要獲取底層對象必須使用buffer屬性。
- **`TypedArray` 提供的9中構造函數的用法**
**(1) `TypedArray(buffer, byteOffset=0, length?)`**
buffer為底層ArrayBuffer,byteOffset為開始字節序號默認為0,length為長度默認直到本段內存區域結束
```JavaScript
// 創建一個8字節的ArrayBuffer
let buf = new ArrayBuffer(8)
// 創建一個指向b的Int32視圖,開始于字節0,直到緩沖區的末尾
let typedAry1 = new Int32Array(buf)
//創建一個指向b的Uint8視圖,開始于字節2,直到緩沖區的末尾
let typedAry2 = new Uin8Array(buf,2)
// 創建一個指向b的Int16視圖,開始于字節2,長度為2
let typedAry2 = new Int16Array(buf,2,2)
```
需要注意的是:byteOffset必須與所要建立的數據類型一致(也就是創建的內存數據字節數要是其視圖規定的整數倍),否則會報錯
**(2) `TypedArray(ArrayObj)` 視圖還可以不通過ArrayBuffer對象,直接分配內存而生成**
```JavaScript
// 直接寫長度,創建一個有八個成員的 32位有符號整數數組(共32個字節)
let typedAry1 = new Int32Array(8)
// 類型化數組的構造函數,可以接受另一個視圖實例作為參數。
let typedArray2 = new Int8Array(new Uint8Array(4));
// 構造函數的參數也可以是一個普通數組,然后直接生成TypedArray實例。
let typedArray3 = new Uint8Array([1, 2, 3, 4]);
```
TypedArray數組也可以轉換回普通數組
```JavaScript
var normalArray = Array.prototype.slice.call(typedArray);
```
- **TypedArray 的其它方法與屬性**
> `TypedArray.prototype.buffer` 返回整段內存區域對應的`ArrayBuffer對象。該屬性為只讀屬性
> `TypedArray.prototype.byteLength` byteLength屬性返回`TypedArray`數組占據的內存長度,單位為字節(只讀)
> `ypedArray.prototype.byteOffset` byteOffset屬性返回`TypedArray`數組從底層`ArrayBuffer`對象的哪個字節開始(只讀)
> `TypedArray.prototype.length` length屬性表示`TypedArray`數組含有多少個成員。注意將`byteLength`屬性和length屬性區分,前者是字節長度,后者是成員長度。
> `TypedArray.prototype.set()` TypedArray數組的set方法用于復制數組(正常數組或TypedArray數組),也就是將一段內容完全復制到另一段內存。
> `TypedArray.prototype.subarray(start,end)` subarray方法是對于`TypedArray`數組的一部分,再建立一個新的視圖。
> `TypedArray.prototype.slice()` TypeArray實例的`slice`方法,可以返回一個指定位置的新的`TypedArray`實例。
> `TypedArray.of()` ypedArray數組的所有構造函數,都有一個靜態方法of,用于將參數轉為一個`TypedArray`實例。
> `TypedArray.from()` 靜態方法from接受一個可遍歷的數據結構(比如數組)作為參數,返回一個基于這個結構的TypedArray實例。
### 三、 `DataView對象`
如果一段數據包括多種類型(比如服務器傳來的HTTP數據),這時除了建立`ArrayBuffer`對象的復合視圖以外,還可以通過`DataView`視圖進行操作。
`DataView`視圖提供更多操作選項,而且支持設定字節序。本來,在設計目的上,`ArrayBuffer`對`象的各種`TypedArray`視圖,是用來向網卡、聲卡之類的本機設備傳送數據,所以使用本機的字節序就可以了;而`DataView`視圖的設計目的,是用來處理網絡設備傳來的數據,所以大端字節序或小端字節序是可以自行設定的。
`DataView`視圖本身也是構造函數,接受一個`ArrayBuffer`對象作為參數,生成視圖。
**1. 使用方法**
```JavaScript
let buffer = new ArrayBuffer(32)
let dv = new DataView(buffer)
```
**2.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方法的參數都是一個字節序號(不能是負數,否則會報錯),表示從哪個字節開始讀取。
```JavaScript
let buffer = new ArrayBuffer(32)
let dv = new DataView(buffer)
// 從第1個字節讀取一個8位無符號整數
var v1 = dv.getUint8(0);
// 從第2個字節讀取一個16位無符號整數
var v2 = dv.getUint16(1);
// 從第4個字節讀取一個16位無符號整數
var v3 = dv.getUint16(3);
```
上面代碼讀取了ArrayBuffer對象的前5個字節,其中有一個8位整數和兩個十六位整數。
如果一次讀取兩個或兩個以上字節,就必須明確數據的存儲方式,到底是小端字節序還是大端字節序。默認情況下,DataView的get方法使用大端字節序解讀數據,如果需要使用小端字節序解讀,必須在get方法的第二個參數指定true。
```JavaScript
// 小端字節序
var v1 = dv.getUint16(1, true);
// 大端字節序
var v2 = dv.getUint16(3, false);
// 大端字節序 默認
var v3 = dv.getUint16(3);
```
**2.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表示使用小端字節序寫入。
```JavaScript
// 在第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);
```