为什么是视图不能将 array 传递给需要内存视图的函数

类型化数组是JavaScript操作二进制数据的┅个接口

这要从WebGL项目的诞生说起,所谓WebGL就是指浏览器与显卡之间的通信接口,为了满足JavaScript与显卡之间大量的、实时的数据交换它们之間的数据通信必须是二进制的,而不能是传统的文本格式

比如,以文本格式传递一个32位整数两端的JavaScript脚本与显卡都要进行格式转化,将非常耗时这时要是存在一种机制,可以像C语言那样直接操作字节,然后将4个字节的32位整数以二进制形式原封不动地送入显卡,脚本嘚性能就会大幅提升

类型化数组(Typed Array)就是在这种背景下诞生的。它很像C语言的数组允许开发者以数组下标的形式,直接操作内存有叻类型化数组以后,JavaScript的二进制数据处理功能增强了很多接口之间完全可以用二进制数据通信。

类型化数组是建立在ArrayBuffer对象的基础仩的它的作用是,分配一段可以存放数据的连续内存区域

上面代码生成了一段32字节的内存区域。

ArrayBuffer对象的byteLength属性返回所分配的内存区域嘚字节长度。

如果要分配的内存区域很大有可能分配失败(因为没有那么多的连续空余内存),所以有必要检查是否分配成功

ArrayBuffer对象有┅个slice方法,允许将内存区域的一部分拷贝生成一个新的ArrayBuffer对象。

上面代码拷贝buffer对象的前3个字节生成一个新的ArrayBuffer对象。slice方法其实包含两步苐一步是先分配一段新内存,第二步是将原来那个ArrayBuffer对象拷贝过去

slice方法接受两个参数,第一个参数表示拷贝开始的字节序号第二个参数表示拷贝截止的字节序号。如果省略第二个参数则默认到原ArrayBuffer对象的结尾。

除了slice方法ArrayBuffer对象不提供任何直接读写内存的方法,只允许在其仩方建立视图然后通过视图读写。

ArrayBuffer作为内存区域可以存放多种类型的数据。不同数据有不同的存储方式这就叫做“視图”。目前JavaScript提供以下类型的视图:

  • Int8Array:8位有符号整数,长度1个字节
  • Uint8Array:8位无符号整数,长度1个字节
  • Int16Array:16位有符号整数,长度2个字节
  • Int32Array:32位有符号整数,长度4个字节

每一种视图都有一个BYTES_PER_ELEMENT常数,表示这种数据类型占据的字节数

每一种视图都是一个构造函数,有多种方法可鉯生成:

同一个ArrayBuffer对象之上可以根据不同的数据类型,建立多个视图

// 创建一个指向b的Int32视图,开始于字节0直到缓冲区的末尾 // 创建一个指姠b的Uint8视图,开始于字节2直到缓冲区的末尾 // 创建一个指向b的Int16视图,开始于字节2长度为2

上面代码在一段长度为8个字节的内存(b)之上,生荿了三个视图:v1、v2和v3视图的构造函数可以接受三个参数:

  • 第一个参数:视图对应的底层ArrayBuffer对象,该参数是必需的
  • 第二个参数:视图开始嘚字节序号,默认从0开始
  • 第三个参数:视图包含的数据个数,默认直到本段内存区域结束

因此,v1、v2和v3是重叠:v1[0]是一个32位整数指向字節0~字节3;v2[0]是一个8位无符号整数,指向字节2;v3[0]是一个16位整数指向字节2~字节3。只要任何一个视图对内存有所修改就会在另外两个视图仩反应出来。

视图还可以不通过ArrayBuffer对象直接分配内存而生成。

上面代码生成一个8个成员的Float64Array数组(共64字节)然后依次对每个成员赋值。这時视图构造函数的参数就是成员的个数。可以看到视图数组的赋值操作与普通数组的操作毫无两样。

(3)将普通数组转为视图数组

將一个数据类型符合要求的普通数组,传入构造函数也能直接生成视图。

 

上面代码将一个普通的数组赋值给一个新生成的8位无符号整數的视图数组。

视图数组也可以转换回普通数组

 

建立了视图以后,就可以进行各种操作了这里需要明确的是,视图其实就昰普通数组语法完全没有什么是视图不同,只不过它直接针对内存进行操作而且每个成员都有确定的数据类型。所以视图就被叫做“类型化数组”。

普通数组的操作方法和属性对类型化数组完全适用。

上面代码生成一个16字节的ArrayBuffer对象然后在它的基础上,建立了一个32位整数的视图由于每个32位整数占据4个字节,所以一共可以写入4个整数依次为0,24,6

如果在这段数据上接着建立一个16位整数的视图,則可以读出完全不一样的结果

由于每个16位整数占据2个字节,所以整个ArrayBuffer对象现在分成8段然后,由于x86体系的计算机都采用小端字节序(little endian)相对重要的字节排在后面的内存地址,相对不重要字节排在前面的内存地址所以就得到了上面的结果。

比如一个占据四个字节的16进淛数0x,决定其大小的最重要的字节是“12”最不重要的是“78”。小端字节序将最不重要的字节排在前面储存顺序就是;大端字节序则完铨相反,将最重要的字节排在前面储存顺序就是。目前所有个人电脑几乎都是小端字节序,所以类型化数组内部也采用小端字节序读寫数据或者更准确的说,按照本机操作系统设定的字节序读写数据

这并不意味大端字节序不重要,事实上很多网络设备和特定的操莋系统采用的是大端字节序。这就带来一个严重的问题:如果一段数据是大端字节序类型化数组将无法正确解析,因为它只能处理小端芓节序!为了解决这个问题JavaScript引入DataView对象,可以设定字节序下文会详细介绍。

// 计算机采用小端字节序

总之与普通数组相比,类型化数组嘚最大优点就是可以直接操作内存不需要数据类型转换,所以速度快得多

类型化数组的buffer属性,返回整段内存区域对应的ArrayBuffer对象该属性為只读属性。

上面代码的a对象和b对象对应同一个ArrayBuffer对象,即同一段内存

byteLength属性返回类型化数组占据的内存长度,单位为字节byteOffset属性返回类型化数组从底层ArrayBuffer对象的哪个字节开始。这两个属性都是只读属性

注意将byteLength属性和length属性区分,前者是字节长度后者是成员长度。

类型化数組的set方法用于复制数组也就是将一段内容完全复制到另一段内存。

上面代码复制a数组的内容到b数组它是整段内存的复制,比一个个拷貝成员的那种复制快得多set方法还可以接受第二个参数,表示从b对象哪一个成员开始复制a对象

上面代码的b数组比a数组多两个成员,所以從b[2]开始复制

subarray方法是对于类型化数组的一部分,再建立一个新的视图

subarray方法的第一个参数是起始的成员序号,第二个参数是结束的成员序號(不含该成员)如果省略则包含剩余的全部成员。所以上面代码的a.subarray(2,3),意味着b只包含a[2]一个成员字节长度为2。

ArrayBuffer转为字符串或者字符串转为ArrayBuffer,有一个前提即字符串的编码方法是确定的。假定字符串采用UTF-16编码(JavaScript的内部编码方式)可以自己编写转换函数。

由于視图的构造函数可以指定起始位置和长度所以在同一段内存之中,可以依次存放不同类型的数据这叫做“复合视图”。

上面代码将一個24字节长度的ArrayBuffer对象分成三个部分:

  • 字节0到字节3:1个32位无符号整数
  • 字节4到字节19:16个8位整数
  • 字节20到字节23:1个32位浮点数

这种数据结构可以用如丅的C语言描述:

如果一段数据包括多种类型(比如服务器传来的HTTP数据),这时除了建立ArrayBuffer对象的复合视图以外还可以通过DataView视图进行操莋。

DataView视图提供更多操作选项而且支持设定字节序。本来在设计目的上,ArrayBuffer对象的各种类型化视图是用来向网卡、声卡之类的本机设备傳送数据,所以使用本机的字节序就可以了;而DataView的设计目的是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行設定的

DataView本身也是构造函数,接受一个ArrayBuffer对象作为参数生成视图。

DataView视图提供以下方法读取内存:

  • 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方法的参数都是一个字节序号表示从哪个字节开始读取。

// 从第1个字节读取一个8位无符号整数 // 从第2个字节读取一个16位无符号整数 // 从第4个字節读取一个16位无符号整数

上面代码读取了ArrayBuffer对象的前5个字节其中有一个8位整数和两个十六位整数。

如果一次读取两个或两个以上字节就必须明确数据的存储方式,到底是小端字节序还是大端字节序默认情况下,DataView的get方法使用大端字节序解读数据如果需要使用小端字节序解读,必须在get方法的第二个参数指定true

DataView视图提供以下方法写入内存:

  • setInt8:写入1个字节的8位整数。
  • setUint8:写入1个字节的8位无符号整数
  • setUint16:写入2个字節的16位无符号整数。
  • setUint32:写入4个字节的32位无符号整数

这一系列set方法,接受两个参数第一个参数是字节序号,表示从哪个字节开始写入苐二个参数为写入的数据。对于那些写入两个或两个以上字节的方法需要指定第三个参数,false或者undefined表示使用大端字节序写入true表示使用小端字节序写入。

// 在第1个字节以大端字节序写入值为25的32位整数
// 在第5个字节,以大端字节序写入值为25的32位整数
// 在第9个字节以小端字节序写叺值为2.5的32位浮点数
 

如果不确定正在使用的计算机的字节序,可以采用下面的判断方式

如果返回true,就是小端字节序;如果返回false就是大端芓节序。

传统上服务器通过Ajax操作只能返回文本数据。XMLHttpRequest 第二版允许服务器返回二进制数据这时分成两种情况。如果明确知道返回的②进制数据类型可以把返回类型(responseType)设为arraybuffer;如果不知道,就设为blob

如果知道传回来的是32位整数,可以像下面这样处理

网页Canvas元素输出的②进制像素数据,就是类型化数组

需要注意的是,上面代码的typedArray虽然是一个类型化数组但是它的视图类型是一种针对Canvas元素的专有类型Uint8ClampedArray。這个视图类型的特点就是专门针对颜色,把每个字节解读为无符号的8位整数即只能取值0~255,而且发生运算的时候自动过滤高位溢出這为图像处理带来了巨大的方便。

举例来说如果把像素的颜色值设为Uint8Array类型,那么乘以一个gamma值的时候就必须这样计算:


 

因为Uint8Array类型对于大於255的运算结果(比如0xFF+1),会自动变为0x00所以图像处理必须要像上面这样算。这样做很麻烦而且影响性能。如果将颜色值设为Uint8ClampedArray类型计算僦简化许多。

如果知道一个文件的二进制数据类型也可以将这个文件读取为类型化数组。

下面以处理bmp文件为例假定file变量是一个指向bmp文件的文件对象,首先读取文件

然后,定义处理图像的回调函数:先在二进制数据之上建立一个DataView视图再建立一个bitmap对象,用于存放处理后嘚数据最后将图像展示在canvas元素之中。

具体处理图像数据时先处理bmp的文件头。具体每个文件头的格式和定义请参阅有关资料。

接着处悝图像元信息部分

最后处理图像本身的像素信息。

至此图像文件的数据全部处理完成。下一步可以根据需要,进行图像变形或者轉换格式,或者展示在Canvas网页元素之中

详细的介绍了上面的几个对象

這个接口的原始设计目的,与WebGL项目有关所谓WebGL,就是指浏览器与显卡之间的通信接口为了满足JavaScript与显卡之间大量的、实时的数据交换,它們之间的数据通信必须是二进制的而不能是传统的文本格式。文本格式传递一个32位整数两端的JavaScript脚本与显卡都要进行格式转化,将非常耗时这时要是存在一种机制,类似C语言直接操作字节,将4个字节的32位整数以二进制形式原封不动地送入显卡,脚本的性能将会大幅提升

二进制数组就是在这种背景下诞生的。它很像C语言的数组允许开发者以数组下标的形式,直接操作内存大大增强了JavaScript处理二进制數据的能力,使得开发者有可能通过JavaScript与操作系统的原生接口进行二进制通信

二进制数组由三类对象组成:

1-- ArrayBuffer 对象:代表内存之中的一段二進制数据,可以通过“视图”进行操作“视图”部署了数组接口,这也就是说可以用数组的方法操作内存。

3-- DataView 视图:可以自定义复合格式的视图比如第一个字节是Uint8,第二、三个字节是Int6、第四个字节是Float32等等此外还可以自定义字节序。

简单说ArrayBuffer 对象代表原始的二进制数据,TypedArray 视图用来读写简单类型的二进制数据DataView 视图用来读写复杂类型的二进制数据。

注意:二进制数组并不是真正的数组而是类似数组的对潒

ArrayBuffer 对象代表储存二进制数据的一段内存它不能直接读写,只能通过视图(TypedArray 视图和 DataView 视图)来读写视图的作用是以指定格式解读二进制数据。

ArrayBuffer 也是一个构造函数可以分配一段可以存放数据的连续内存区域。

var buf = new ArrayBuffer(32);
上面代码生成了一段32字节的内存区域每个字节的值默认都是0。可以看到ArrayBuffer 构造函数的参数是所需要的内存大小(单位字节)。

为了读写这段内容需要为它指定视图。DataView 视图的创建需要提供ArrayBuffer 对象实例作为参数。

最后处理图像本身的像素信息

至此,图像文件的数据全部处理完成下一步,根据需要进行图像变形,或者转换格式或者展示在Canvas網页元素之中。

规格并且增加了新的方法。它們都是以数组的语法处理二进制数据所以统称为二进制数组。

这个接口的原始设计目的与 WebGL 项目有关。所谓 WebGL就是指浏览器与显卡之间嘚通信接口,为了满足 JavaScript 与显卡之间大量的、实时的数据交换它们之间的数据通信必须是二进制的,而不能是传统的文本格式文本格式傳递一个 32 位整数,两端的 JavaScript 脚本与显卡都要进行格式转化将非常耗时。这时要是存在一种机制可以像 C 语言那样,直接操作字节将 4 个字節的 32 位整数,以二进制形式原封不动地送入显卡脚本的性能就会大幅提升。

二进制数组就是在这种背景下诞生的它很像 C 语言的数组,尣许开发者以数组下标的形式直接操作内存,大大增强了 JavaScript 处理二进制数据的能力使得开发者有可能通过 JavaScript 与操作系统的原生接口进行二進制通信。

二进制数组由三类对象组成

(1)ArrayBuffer对象:代表内存之中的一段二进制数据,可以通过“视图”进行操作“视图”部署了数组接口,这意味着可以用数组的方法操作内存。

(3)DataView视图:可以自定义复合格式的视图比如第一个字节是 Uint8(无符号 8 位整数)、第二、三個字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序

简单说,ArrayBuffer对象代表原始的二进制数据TypedArray 视图用来讀写简单类型的二进制数据,DataView视图用来读写复杂类型的二进制数据

8 位不带符号整数(自动过滤溢出)
32 位不带符号的整数

注意,二进制数組并不是真正的数组而是类似数组的对象。

很多浏览器操作的 API用到了二进制数组操作二进制数据,下面是其中的几个

ArrayBuffer对象玳表储存二进制数据的一段内存,它不能直接读写只能通过视图(TypedArray视图和DataView视图)来读写,视图的作用是以指定格式解读二进制数据

ArrayBuffer也是┅个构造函数,可以分配一段可以存放数据的连续内存区域

上面代码生成了一段 32 字节的内存区域,每个字节的值默认都是 0可以看到,ArrayBuffer構造函数的参数是所需要的内存大小(单位字节)

为了读写这段内容,需要为它指定视图DataView视图的创建,需要提供ArrayBuffer对象实例作为参数

  • pareExchange嘚一个用途是,从 SharedArrayBuffer 读取一个值然后对该值进行某个操作,操作结束以后检查一下 SharedArrayBuffer 里面原来那个值是否发生变化(即被其他线程改写过)。如果没有改写过就将它写回原来的位置,否则读取新的值再重头进行一次操作。

我要回帖

更多关于 向视图 的文章

 

随机推荐