CocosCreator 源码./polyfill/array-buffer详解

/*

 ArrayBuffer.isView() 方法用来判断传入的参数值是否是一种 ArrayBuffer 视图(view),

 比如类型化数组对象(typed array objects)或者数据视图( 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,二进制数组

在 Web 开发中,当我们处理文件时(创建,上传,下载),经常会遇到二进制数据。另一个典型的应用场景是图像处理。

这些都可以通过 JavaScript 进行处理,而且二进制操作性能更高。

不过,在 JavaScript 中有很多种二进制数据格式,会有点容易混淆。仅举几个例子:

ArrayBuffer,Uint8Array,DataView,Blob,File及其他。

与其他语言相比,JavaScript 中的二进制数据是以非标准方式实现的。但是,当我们理清楚以后,一切就会变得相当简单了。

基本的二进制对象是ArrayBuffer—— 对固定长度的连续内存空间的引用。

我们这样创建它:

letbuffer=newArrayBuffer(16);// 创建一个长度为 16 的 buffer

alert(buffer.byteLength);// 16

它会分配一个 16 字节的连续内存空间,并用 0 进行预填充。

ArrayBuffer不是某种东西的数组

让我们先澄清一个可能的误区。ArrayBuffer与Array没有任何共同之处:

它的长度是固定的,我们无法增加或减少它的长度。

它正好占用了内存中的那么多空间。

要访问单个字节,需要另一个“视图”对象,而不是buffer[index]。

ArrayBuffer是一个内存区域。它里面存储了什么?无从判断。只是一个原始的字节序列。

如要操作ArrayBuffer,我们需要使用“视图”对象。

视图对象本身并不存储任何东西。它是一副“眼镜”,透过它来解释存储在ArrayBuffer中的字节。

例如:

Uint8Array—— 将ArrayBuffer中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位,因此只能容纳那么多)。这称为 “8 位无符号整数”。

Uint16Array—— 将每 2 个字节视为一个 0 到 65535 之间的整数。这称为 “16 位无符号整数”。

Uint32Array—— 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”。

Float64Array—— 将每 8 个字节视为一个5.0x10-324到1.8x10308之间的浮点数。

因此,一个 16 字节ArrayBuffer中的二进制数据可以解释为 16 个“小数字”,或 8 个更大的数字(每个数字 2 个字节),或 4 个更大的数字(每个数字 4 个字节),或 2 个高精度的浮点数(每个数字 8 个字节)。

ArrayBuffer是核心对象,是所有的基础,是原始的二进制数据。

但是,如果我们要写入值或遍历它,基本上几乎所有操作 —— 我们必须使用视图(view),例如:

letbuffer=newArrayBuffer(16);// 创建一个长度为 16 的 buffer

letview=newUint32Array(buffer);// 将 buffer 视为一个 32 位整数的序列

alert(Uint32Array.BYTES_PER_ELEMENT);// 每个整数 4 个字节

alert(view.length);// 4,它存储了 4 个整数

alert(view.byteLength);// 16,字节中的大小

// 让我们写入一个值

view[0]=123456;

// 遍历值

for(letnumofview){

alert(num);// 123456,然后 0,0,0(一共 4 个值)

}

TypedArray

所有这些视图(Uint8Array,Uint32Array等)的通用术语是 TypedArray。它们共享同一方法和属性集。

请注意,没有名为TypedArray的构造器,它只是表示ArrayBuffer上的视图之一的通用总称术语:Int8Array,Uint8Array及其他,很快就会有完整列表。

当你看到new TypedArray之类的内容时,它表示new Int8Array、new Uint8Array及其他中之一。

类型化数组的行为类似于常规数组:具有索引,并且是可迭代的。

一个类型化数组的构造器(无论是Int8Array或Float64Array,都无关紧要),其行为各不相同,并且取决于参数类型。

参数有 5 种变体:

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

newTypedArray(object);

newTypedArray(typedArray);

newTypedArray(length);

newTypedArray();

如果给定的是ArrayBuffer参数,则会在其上创建视图。我们已经用过该语法了。

可选,我们可以给定起始位置byteOffset(默认为 0)以及length(默认至 buffer 的末尾),这样视图将仅涵盖buffer的一部分。

如果给定的是Array,或任何类数组对象,则会创建一个相同长度的类型化数组,并复制其内容。

我们可以使用它来预填充数组的数据:

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

alert(arr.length);// 4,创建了相同长度的二进制数组

alert(arr[1]);// 1,用给定值填充了 4 个字节(无符号 8 位整数)

如果给定的是另一个TypedArray,也是如此:创建一个相同长度的类型化数组,并复制其内容。如果需要的话,数据在此过程中会被转换为新的类型。

letarr16=newUint16Array([1,1000]);

letarr8=newUint8Array(arr16);

alert(arr8[0]);// 1

alert(arr8[1]);// 232,试图复制 1000,但无法将 1000 放进 8 位字节中(详述见下文)。

对于数字参数length—— 创建类型化数组以包含这么多元素。它的字节长度将是length乘以单个TypedArray.BYTES_PER_ELEMENT中的字节数:

letarr=newUint16Array(4);// 为 4 个整数创建类型化数组

alert(Uint16Array.BYTES_PER_ELEMENT);// 每个整数 2 个字节

alert(arr.byteLength);// 8(字节中的大小)

不带参数的情况下,创建长度为零的类型化数组。

我们可以直接创建一个TypedArray,而无需提及ArrayBuffer。但是,视图离不开底层的ArrayBuffer,因此,除第一种情况(已提供ArrayBuffer)外,其他所有情况都会自动创建ArrayBuffer。

如要访问底层的ArrayBuffer,那么在TypedArray中有如下的属性:

arr.buffer—— 引用ArrayBuffer。

arr.byteLength——ArrayBuffer的长度。

因此,我们总是可以从一个视图转到另一个视图:

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

// 同一数据的另一个视图

letarr16=newUint16Array(arr8.buffer);

下面是类型化数组的列表:

Uint8Array,Uint16Array,Uint32Array—— 用于 8、16 和 32 位的整数。

Uint8ClampedArray—— 用于 8 位整数,在赋值时便“固定“其值(见下文)。

Int8Array,Int16Array,Int32Array—— 用于有符号整数(可以为负数)。

Float32Array,Float64Array—— 用于 32 位和 64 位的有符号浮点数。

没有int8或类似的单值类型

请注意,尽管有类似Int8Array这样的名称,但 JavaScript 中并没有像int,或int8这样的单值类型。

这是合乎逻辑的,因为Int8Array不是这些单值的数组,而是ArrayBuffer上的视图。

越界行为

如果我们尝试将越界值写入类型化数组会出现什么情况?不会报错。但是多余的位被切除。

例如,我们尝试将 256 放入Uint8Array。256 的二进制格式是100000000(9 位),但Uint8Array每个值只有 8 位,因此可用范围为 0 到 255。

对于更大的数字,仅存储最右边的(低位有效)8 位,其余部分被切除:

因此结果是 0。

257 的二进制格式是100000001(9 位),最右边的 8 位会被存储,因此数组中会有1:

换句话说,该数字对 28取模的结果被保存了下来。

示例如下:

letuint8array=newUint8Array(16);

letnum=256;

alert(num.toString(2));// 100000000(二进制表示)

uint8array[0]=256;

uint8array[1]=257;

alert(uint8array[0]);// 0

alert(uint8array[1]);// 1

Uint8ClampedArray在这方面比较特殊,它的表现不太一样。对于大于 255 的任何数字,它将保存为 255,对于任何负数,它将保存为 0。此行为对于图像处理很有用。

TypedArray 方法

TypedArray具有常规的Array方法,但有个明显的例外。

我们可以遍历(iterate),map,slice,find和reduce等。

但有几件事我们做不了:

没有splice—— 我们无法“删除”一个值,因为类型化数组是缓冲区(buffer)上的视图,并且缓冲区(buffer)是固定的、连续的内存区域。我们所能做的就是分配一个零值。

无concat方法。

还有两种其他方法:

arr.set(fromArr, [offset])从offset(默认为 0)开始,将fromArr中的所有元素复制到arr。

arr.subarray([begin, end])创建一个从begin到end(不包括)相同类型的新视图。这类似于slice方法(同样也支持),但不复制任何内容 —— 只是创建一个新视图,以对给定片段的数据进行操作。

有了这些方法,我们可以复制、混合类型化数组,从现有数组创建新数组等。

DataView

DataView 是在ArrayBuffer上的一种特殊的超灵活“未类型化”视图。它允许以任何格式访问任何偏移量(offset)的数据。

对于类型化的数组,构造器决定了其格式。整个数组应该是统一的。第 i 个数字是arr[i]。

通过DataView,我们可以使用.getUint8(i)或.getUint16(i)之类的方法访问数据。我们在调用方法时选择格式,而不是在构造的时候。

语法:

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

buffer—— 底层的ArrayBuffer。与类型化数组不同,DataView不会自行创建缓冲区(buffer)。我们需要事先准备好。

byteOffset—— 视图的起始字节位置(默认为 0)。

byteLength—— 视图的字节长度(默认至buffer的末尾)。

例如,这里我们从同一个 buffer 中提取不同格式的数字:

// 4 个字节的二进制数组,每个都是最大值 255

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

letdataView=newDataView(buffer);

// 在偏移量为 0 处获取 8 位数字

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

// 现在在偏移量为 0 处获取 16 位数字,它由 2 个字节组成,一起解析为 65535

alert(dataView.getUint16(0));// 65535(最大的 16 位无符号整数)

// 在偏移量为 0 处获取 32 位数字

alert(dataView.getUint32(0));// 4294967295(最大的 32 位无符号整数)

dataView.setUint32(0,0);// 将 4 个字节的数字设为 0,即将所有字节都设为 0

当我们将混合格式的数据存储在同一缓冲区(buffer)中时,DataView非常有用。例如,当我们存储一个成对序列(16 位整数,32 位浮点数)时,用DataView可以轻松访问它们。

总结

ArrayBuffer是核心对象,是对固定长度的连续内存区域的引用。

几乎任何对ArrayBuffer的操作,都需要一个视图。

它可以是TypedArray:

Uint8Array,Uint16Array,Uint32Array—— 用于 8 位、16 位和 32 位无符号整数。

Uint8ClampedArray—— 用于 8 位整数,在赋值时便“固定”其值。

Int8Array,Int16Array,Int32Array—— 用于有符号整数(可以为负数)。

Float32Array,Float64Array—— 用于 32 位和 64 位的有符号浮点数。

或DataView—— 使用方法来指定格式的视图,例如,getUint8(offset)。

在大多数情况下,我们直接对类型化数组进行创建和操作,而将ArrayBuffer作为“共同之处(common denominator)”隐藏起来。我们可以通过.buffer来访问它,并在需要时创建另一个视图。

还有另外两个术语,用于对二进制数据进行操作的方法的描述:

ArrayBufferView是所有这些视图的总称。

BufferSource是ArrayBuffer或ArrayBufferView的总称。

我们将在下一章中学习这些术语。BufferSource是最常用的术语之一,因为它的意思是“任何类型的二进制数据” ——ArrayBuffer或其上的视图。

这是一份备忘单:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,815评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,251评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 171,999评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,996评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,993评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,477评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,848评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,823评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,361评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,401评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,518评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,119评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,850评论 3 338
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,292评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,424评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,072评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,588评论 2 365

推荐阅读更多精彩内容