CocosCreator 源碼./polyfill/array-buffer詳解

/*

?ArrayBuffer.isView() 方法用來判斷傳入的參數(shù)值是否是一種 ArrayBuffer 視圖(view),

?比如類型化數(shù)組對(duì)象(typed array objects)或者數(shù)據(jù)視圖( DataView)。

?*/

if (!ArrayBuffer.isView) {

? ? const TypedArray = Object.getPrototypeOf(Int8Array);

? ? ArrayBuffer.isView = (typeof TypedArray === 'function') ? function (obj) {

? ? ? ? return obj instanceof TypedArray;

? ? } : function (obj) {

? ? ? ? // old JSC, phantom, QtWebview

? ? ? ? if (typeof obj !== 'object') {

? ? ? ? ? ? return false;

? ? ? ? }

? ? ? ? let ctor = obj.constructor;

? ? ? ? return ctor === Float64Array || ctor === Float32Array || ctor === Uint8Array || ctor === Uint32Array || ctor === Int8Array;

? ? };

}

ArrayBuffer,二進(jìn)制數(shù)組

在 Web 開發(fā)中,當(dāng)我們處理文件時(shí)(創(chuàng)建,上傳,下載),經(jīng)常會(huì)遇到二進(jìn)制數(shù)據(jù)。另一個(gè)典型的應(yīng)用場(chǎng)景是圖像處理。

這些都可以通過 JavaScript 進(jìn)行處理,而且二進(jìn)制操作性能更高。

不過,在 JavaScript 中有很多種二進(jìn)制數(shù)據(jù)格式,會(huì)有點(diǎn)容易混淆。僅舉幾個(gè)例子:

ArrayBuffer,Uint8Array,DataView,Blob,F(xiàn)ile及其他。

與其他語言相比,JavaScript 中的二進(jìn)制數(shù)據(jù)是以非標(biāo)準(zhǔn)方式實(shí)現(xiàn)的。但是,當(dāng)我們理清楚以后,一切就會(huì)變得相當(dāng)簡(jiǎn)單了。

基本的二進(jìn)制對(duì)象是ArrayBuffer—— 對(duì)固定長(zhǎng)度的連續(xù)內(nèi)存空間的引用。

我們這樣創(chuàng)建它:

letbuffer=newArrayBuffer(16);// 創(chuàng)建一個(gè)長(zhǎng)度為 16 的 buffer

alert(buffer.byteLength);// 16

它會(huì)分配一個(gè) 16 字節(jié)的連續(xù)內(nèi)存空間,并用 0 進(jìn)行預(yù)填充。

ArrayBuffer不是某種東西的數(shù)組

讓我們先澄清一個(gè)可能的誤區(qū)。ArrayBuffer與Array沒有任何共同之處:

它的長(zhǎng)度是固定的,我們無法增加或減少它的長(zhǎng)度。

它正好占用了內(nèi)存中的那么多空間。

要訪問單個(gè)字節(jié),需要另一個(gè)“視圖”對(duì)象,而不是buffer[index]。

ArrayBuffer是一個(gè)內(nèi)存區(qū)域。它里面存儲(chǔ)了什么?無從判斷。只是一個(gè)原始的字節(jié)序列。

如要操作ArrayBuffer,我們需要使用“視圖”對(duì)象。

視圖對(duì)象本身并不存儲(chǔ)任何東西。它是一副“眼鏡”,透過它來解釋存儲(chǔ)在ArrayBuffer中的字節(jié)。

例如:

Uint8Array—— 將ArrayBuffer中的每個(gè)字節(jié)視為 0 到 255 之間的單個(gè)數(shù)字(每個(gè)字節(jié)是 8 位,因此只能容納那么多)。這稱為 “8 位無符號(hào)整數(shù)”。

Uint16Array—— 將每 2 個(gè)字節(jié)視為一個(gè) 0 到 65535 之間的整數(shù)。這稱為 “16 位無符號(hào)整數(shù)”。

Uint32Array—— 將每 4 個(gè)字節(jié)視為一個(gè) 0 到 4294967295 之間的整數(shù)。這稱為 “32 位無符號(hào)整數(shù)”。

Float64Array—— 將每 8 個(gè)字節(jié)視為一個(gè)5.0x10-324到1.8x10308之間的浮點(diǎn)數(shù)。

因此,一個(gè) 16 字節(jié)ArrayBuffer中的二進(jìn)制數(shù)據(jù)可以解釋為 16 個(gè)“小數(shù)字”,或 8 個(gè)更大的數(shù)字(每個(gè)數(shù)字 2 個(gè)字節(jié)),或 4 個(gè)更大的數(shù)字(每個(gè)數(shù)字 4 個(gè)字節(jié)),或 2 個(gè)高精度的浮點(diǎn)數(shù)(每個(gè)數(shù)字 8 個(gè)字節(jié))。

ArrayBuffer是核心對(duì)象,是所有的基礎(chǔ),是原始的二進(jìn)制數(shù)據(jù)。

但是,如果我們要寫入值或遍歷它,基本上幾乎所有操作 —— 我們必須使用視圖(view),例如:

letbuffer=newArrayBuffer(16);// 創(chuàng)建一個(gè)長(zhǎng)度為 16 的 buffer

letview=newUint32Array(buffer);// 將 buffer 視為一個(gè) 32 位整數(shù)的序列

alert(Uint32Array.BYTES_PER_ELEMENT);// 每個(gè)整數(shù) 4 個(gè)字節(jié)

alert(view.length);// 4,它存儲(chǔ)了 4 個(gè)整數(shù)

alert(view.byteLength);// 16,字節(jié)中的大小

// 讓我們寫入一個(gè)值

view[0]=123456;

// 遍歷值

for(letnumofview){

alert(num);// 123456,然后 0,0,0(一共 4 個(gè)值)

}

TypedArray

所有這些視圖(Uint8Array,Uint32Array等)的通用術(shù)語是?TypedArray。它們共享同一方法和屬性集。

請(qǐng)注意,沒有名為TypedArray的構(gòu)造器,它只是表示ArrayBuffer上的視圖之一的通用總稱術(shù)語:Int8Array,Uint8Array及其他,很快就會(huì)有完整列表。

當(dāng)你看到new TypedArray之類的內(nèi)容時(shí),它表示new Int8Array、new Uint8Array及其他中之一。

類型化數(shù)組的行為類似于常規(guī)數(shù)組:具有索引,并且是可迭代的。

一個(gè)類型化數(shù)組的構(gòu)造器(無論是Int8Array或Float64Array,都無關(guān)緊要),其行為各不相同,并且取決于參數(shù)類型。

參數(shù)有 5 種變體:

newTypedArray(buffer,[byteOffset],[length]);

newTypedArray(object);

newTypedArray(typedArray);

newTypedArray(length);

newTypedArray();

如果給定的是ArrayBuffer參數(shù),則會(huì)在其上創(chuàng)建視圖。我們已經(jīng)用過該語法了。

可選,我們可以給定起始位置byteOffset(默認(rèn)為 0)以及l(fā)ength(默認(rèn)至 buffer 的末尾),這樣視圖將僅涵蓋buffer的一部分。

如果給定的是Array,或任何類數(shù)組對(duì)象,則會(huì)創(chuàng)建一個(gè)相同長(zhǎng)度的類型化數(shù)組,并復(fù)制其內(nèi)容。

我們可以使用它來預(yù)填充數(shù)組的數(shù)據(jù):

letarr=newUint8Array([0,1,2,3]);

alert(arr.length);// 4,創(chuàng)建了相同長(zhǎng)度的二進(jìn)制數(shù)組

alert(arr[1]);// 1,用給定值填充了 4 個(gè)字節(jié)(無符號(hào) 8 位整數(shù))

如果給定的是另一個(gè)TypedArray,也是如此:創(chuàng)建一個(gè)相同長(zhǎng)度的類型化數(shù)組,并復(fù)制其內(nèi)容。如果需要的話,數(shù)據(jù)在此過程中會(huì)被轉(zhuǎn)換為新的類型。

letarr16=newUint16Array([1,1000]);

letarr8=newUint8Array(arr16);

alert(arr8[0]);// 1

alert(arr8[1]);// 232,試圖復(fù)制 1000,但無法將 1000 放進(jìn) 8 位字節(jié)中(詳述見下文)。

對(duì)于數(shù)字參數(shù)length—— 創(chuàng)建類型化數(shù)組以包含這么多元素。它的字節(jié)長(zhǎng)度將是length乘以單個(gè)TypedArray.BYTES_PER_ELEMENT中的字節(jié)數(shù):

letarr=newUint16Array(4);// 為 4 個(gè)整數(shù)創(chuàng)建類型化數(shù)組

alert(Uint16Array.BYTES_PER_ELEMENT);// 每個(gè)整數(shù) 2 個(gè)字節(jié)

alert(arr.byteLength);// 8(字節(jié)中的大?。?/p>

不帶參數(shù)的情況下,創(chuàng)建長(zhǎng)度為零的類型化數(shù)組。

我們可以直接創(chuàng)建一個(gè)TypedArray,而無需提及ArrayBuffer。但是,視圖離不開底層的ArrayBuffer,因此,除第一種情況(已提供ArrayBuffer)外,其他所有情況都會(huì)自動(dòng)創(chuàng)建ArrayBuffer。

如要訪問底層的ArrayBuffer,那么在TypedArray中有如下的屬性:

arr.buffer—— 引用ArrayBuffer。

arr.byteLength——ArrayBuffer的長(zhǎng)度。

因此,我們總是可以從一個(gè)視圖轉(zhuǎn)到另一個(gè)視圖:

letarr8=newUint8Array([0,1,2,3]);

// 同一數(shù)據(jù)的另一個(gè)視圖

letarr16=newUint16Array(arr8.buffer);

下面是類型化數(shù)組的列表:

Uint8Array,Uint16Array,Uint32Array—— 用于 8、16 和 32 位的整數(shù)。

Uint8ClampedArray—— 用于 8 位整數(shù),在賦值時(shí)便“固定“其值(見下文)。

Int8Array,Int16Array,Int32Array—— 用于有符號(hào)整數(shù)(可以為負(fù)數(shù))。

Float32Array,F(xiàn)loat64Array—— 用于 32 位和 64 位的有符號(hào)浮點(diǎn)數(shù)。

沒有int8或類似的單值類型

請(qǐng)注意,盡管有類似Int8Array這樣的名稱,但 JavaScript 中并沒有像int,或int8這樣的單值類型。

這是合乎邏輯的,因?yàn)镮nt8Array不是這些單值的數(shù)組,而是ArrayBuffer上的視圖。

越界行為

如果我們嘗試將越界值寫入類型化數(shù)組會(huì)出現(xiàn)什么情況?不會(huì)報(bào)錯(cuò)。但是多余的位被切除。

例如,我們嘗試將 256 放入U(xiǎn)int8Array。256 的二進(jìn)制格式是100000000(9 位),但Uint8Array每個(gè)值只有 8 位,因此可用范圍為 0 到 255。

對(duì)于更大的數(shù)字,僅存儲(chǔ)最右邊的(低位有效)8 位,其余部分被切除:

因此結(jié)果是 0。

257 的二進(jìn)制格式是100000001(9 位),最右邊的 8 位會(huì)被存儲(chǔ),因此數(shù)組中會(huì)有1:

換句話說,該數(shù)字對(duì) 28取模的結(jié)果被保存了下來。

示例如下:

letuint8array=newUint8Array(16);

letnum=256;

alert(num.toString(2));// 100000000(二進(jìn)制表示)

uint8array[0]=256;

uint8array[1]=257;

alert(uint8array[0]);// 0

alert(uint8array[1]);// 1

Uint8ClampedArray在這方面比較特殊,它的表現(xiàn)不太一樣。對(duì)于大于 255 的任何數(shù)字,它將保存為 255,對(duì)于任何負(fù)數(shù),它將保存為 0。此行為對(duì)于圖像處理很有用。

TypedArray 方法

TypedArray具有常規(guī)的Array方法,但有個(gè)明顯的例外。

我們可以遍歷(iterate),map,slice,find和reduce等。

但有幾件事我們做不了:

沒有splice—— 我們無法“刪除”一個(gè)值,因?yàn)轭愋突瘮?shù)組是緩沖區(qū)(buffer)上的視圖,并且緩沖區(qū)(buffer)是固定的、連續(xù)的內(nèi)存區(qū)域。我們所能做的就是分配一個(gè)零值。

無concat方法。

還有兩種其他方法:

arr.set(fromArr, [offset])從offset(默認(rèn)為 0)開始,將fromArr中的所有元素復(fù)制到arr。

arr.subarray([begin, end])創(chuàng)建一個(gè)從begin到end(不包括)相同類型的新視圖。這類似于slice方法(同樣也支持),但不復(fù)制任何內(nèi)容 —— 只是創(chuàng)建一個(gè)新視圖,以對(duì)給定片段的數(shù)據(jù)進(jìn)行操作。

有了這些方法,我們可以復(fù)制、混合類型化數(shù)組,從現(xiàn)有數(shù)組創(chuàng)建新數(shù)組等。

DataView

DataView?是在ArrayBuffer上的一種特殊的超靈活“未類型化”視圖。它允許以任何格式訪問任何偏移量(offset)的數(shù)據(jù)。

對(duì)于類型化的數(shù)組,構(gòu)造器決定了其格式。整個(gè)數(shù)組應(yīng)該是統(tǒng)一的。第 i 個(gè)數(shù)字是arr[i]。

通過DataView,我們可以使用.getUint8(i)或.getUint16(i)之類的方法訪問數(shù)據(jù)。我們?cè)谡{(diào)用方法時(shí)選擇格式,而不是在構(gòu)造的時(shí)候。

語法:

newDataView(buffer,[byteOffset],[byteLength])

buffer—— 底層的ArrayBuffer。與類型化數(shù)組不同,DataView不會(huì)自行創(chuàng)建緩沖區(qū)(buffer)。我們需要事先準(zhǔn)備好。

byteOffset—— 視圖的起始字節(jié)位置(默認(rèn)為 0)。

byteLength—— 視圖的字節(jié)長(zhǎng)度(默認(rèn)至buffer的末尾)。

例如,這里我們從同一個(gè) buffer 中提取不同格式的數(shù)字:

// 4 個(gè)字節(jié)的二進(jìn)制數(shù)組,每個(gè)都是最大值 255

letbuffer=newUint8Array([255,255,255,255]).buffer;

letdataView=newDataView(buffer);

// 在偏移量為 0 處獲取 8 位數(shù)字

alert(dataView.getUint8(0));// 255

// 現(xiàn)在在偏移量為 0 處獲取 16 位數(shù)字,它由 2 個(gè)字節(jié)組成,一起解析為 65535

alert(dataView.getUint16(0));// 65535(最大的 16 位無符號(hào)整數(shù))

// 在偏移量為 0 處獲取 32 位數(shù)字

alert(dataView.getUint32(0));// 4294967295(最大的 32 位無符號(hào)整數(shù))

dataView.setUint32(0,0);// 將 4 個(gè)字節(jié)的數(shù)字設(shè)為 0,即將所有字節(jié)都設(shè)為 0

當(dāng)我們將混合格式的數(shù)據(jù)存儲(chǔ)在同一緩沖區(qū)(buffer)中時(shí),DataView非常有用。例如,當(dāng)我們存儲(chǔ)一個(gè)成對(duì)序列(16 位整數(shù),32 位浮點(diǎn)數(shù))時(shí),用DataView可以輕松訪問它們。

總結(jié)

ArrayBuffer是核心對(duì)象,是對(duì)固定長(zhǎng)度的連續(xù)內(nèi)存區(qū)域的引用。

幾乎任何對(duì)ArrayBuffer的操作,都需要一個(gè)視圖。

它可以是TypedArray:

Uint8Array,Uint16Array,Uint32Array—— 用于 8 位、16 位和 32 位無符號(hào)整數(shù)。

Uint8ClampedArray—— 用于 8 位整數(shù),在賦值時(shí)便“固定”其值。

Int8Array,Int16Array,Int32Array—— 用于有符號(hào)整數(shù)(可以為負(fù)數(shù))。

Float32Array,F(xiàn)loat64Array—— 用于 32 位和 64 位的有符號(hào)浮點(diǎn)數(shù)。

或DataView—— 使用方法來指定格式的視圖,例如,getUint8(offset)。

在大多數(shù)情況下,我們直接對(duì)類型化數(shù)組進(jìn)行創(chuàng)建和操作,而將ArrayBuffer作為“共同之處(common denominator)”隱藏起來。我們可以通過.buffer來訪問它,并在需要時(shí)創(chuàng)建另一個(gè)視圖。

還有另外兩個(gè)術(shù)語,用于對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行操作的方法的描述:

ArrayBufferView是所有這些視圖的總稱。

BufferSource是ArrayBuffer或ArrayBufferView的總稱。

我們將在下一章中學(xué)習(xí)這些術(shù)語。BufferSource是最常用的術(shù)語之一,因?yàn)樗囊馑际恰叭魏晤愋偷亩M(jìn)制數(shù)據(jù)” ——ArrayBuffer或其上的視圖。

這是一份備忘單:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容