二进制数组(上)
二进制数组(ArrayBuffer对象
、TypedArray视图
和DataView视图
)是JavaScript操作二进制数据的一个接口。这些对象早就存在,属于独立的规格(2011年2月发布),ES6将它们纳入了ECMAScript规格,并且增加了新的方法。
这个接口的原始设计目的与WebGL项目
有关,所谓WebGL
,就是浏览器与显卡之间的通信接口,为了满足JavaScript与显卡之间大量、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。文本格式传递一个32位整数,两端的JavaScript脚本与显卡都要进行格式化,将非常耗时。这时要是存在一种机制,可以像C语言
那样直接操作字节,将4个字节的32位整数以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。
二进制数组就是在这背景下诞生的。它很像C语言
的数组,允许开发者以数组下标的形式直接操作内存,大大增强了JavaScript处理二进制数据的能力,使开发都有可能通过JavaScript与操作系统的原生接口进行二进制通信。
二进制数组由3类对象组成。
-
ArrayBuffer对象
:代表内存 中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方式操作内存。 -
TypedArray视图
:共包括9种类型的视图,比如Uint8Array
(无符号8位整数)数组视图、Int16Array
(16位整数)数组视图、Float32Array
(32位浮点数)数组视图等。 -
DataView视图
:可以自定义复合格式的视图,比如第一个字节是Uint8
、第二和第三个字符是Int16
、第四个字节开始是Float32
等,此外还可以自定义节序。
简而言之,ArrayBuffer对象
代表原始的二进制数据,TypedArray视图
用于读/写简单类型的二进制数据,DataView视图
用于读/写复杂类型的二进制数据。
TypedArray视图
支持的数据类型一共有9种(DataView视图
支持除Uint8C
以外的其他8种)。
数据类型 | 字节长度 | 含义 | 对应的C语言类型 |
---|---|---|---|
Int8 | 1 | 8位带符号整数 | singed 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用到了二进制数组操作二进制数据,下面是其中几个。
- File API
- XMLHttpRequest
- Fetch API
- Canvas
- WebSockets
ArrayBuffer对象
概述
ArrayBuffer对象
代表储存二进制数据的一段内存,它不能直接读/写,只能通过视图(TypedArray视图
和DataView视图
)读/写,视图的作用是以指定格式解读二进制数据。
ArrayBuffer
也是一个构造函数,可分配一段可以存放数据的连续内存区域。
js
var buf = new ArrayBuffer(32);
上面的代码生成了一段32字节的内存区域,每个字节的值默认都是0。可以看到,ArrayBuffer构造函数
的参数是所需要的内存大小(单位:字节)。
为了读/写这段内存,需要为它指定视图。创建DataView视图
,需要提供ArrayBuffer对象实例
作为参数。
js
var buf = new ArrayBuffer(32);
var dataView = new DataView(buf);
dataView.getUint8(0) // 0
上面的代码对一段32字节的内存建立DataView视图
,然后以不带符号的8位整数格式读取第一个元素,结果得到0,因为原始内存的ArrayBuffer对象
默认所有位都是0。
TypedArray视图
与DataView视图
的一个区别是,它不是一个构造函数,而是一组构造函数,代表不同的数据格式。
js
var buffer = new ArrayBuffer(12);
var x1 = new Int32Array(buffer);
x1[0] = 1;
var x2 = new Uint8Array(buffer);
x2[0] = 2;
上面的代码对同一段内存分别建立了两种视图:32位带符号整数和8位不带符号整数。由于两个视图对应的是同一段内存,因此一个视图修改底层内存会影响到另一个视图。
TypedArray视图
的构造函数除了接受ArrayBuffer实例
作为参数,还可以接受普通数组作为参数,直接分配内存生成底层的ArrayBuffer实例
,同时完成对这段内存的赋值。
js
var typedArray = new Uint8Array([0, 1, 2]);
typedArray.length // 3
typedArray[0] = 5
typedArray // [5, 1, 2]
上面的代码使用TypedArray视图
作为Uint8Array构造函数
新建一个不带符号的8位整数视图。可以看到,Uint8Array
直接使用普通数组作为参数,对底层内存的赋值同时完成。
ArrayBuffer.prototype.byteLength
ArrayBuffer实例
的byteLength属性
返回所分配的内存区域的字节长度。
js
var buffer = new ArrayBuffer(32);
buffer.byteLength // 32
如果要分配的内存区域很大,有可能分配失败(因为没有那么多的连续空余内存),所以有必要检查是否分配成功。
js
if (buffer.byteLength === n) {
// 成功
} else {
// 失败
}
ArrayBuffer.prototype.slice()
ArrayBuffer实例
有一个slice方法
,允许将内存区域的一部分复制生成一个新的ArrayBuffer对象
。
js
var buffer = new ArrayBuffer(8);
var newBuffer = buffer.slice(0, 3);
上面的代码复制buffer对象
的前3个字节(从0开始,到第3个字节前面结束),生成一个新的ArrayBuffer对象
。slice方法
其实包含两步:第一步先分配一段新的内存,第二步将原来那个ArrayBuffer对象
复制过去。
slice方法
接受两个参数:第一个参数表示复制开始的字节序号(含该字节),第二个参数表示复制截止的字节序号(不含该字节)。如果省略第二个参数,则默认到原ArrayBuffer对象
的结尾。
除了slice方法
,ArrayBuffer对象
不提供任何读/写内存的方法,只允许在其上建立视图,然后通过视图读/写。
ArrayBuffer.isView()
ArrayBuffer
有一个静态方法isView
,返回一个布尔值,表示参数是否为ArrayBuffer
的视图实例。这个方法大致相当于判断参数是否为TypedArray实例
或DataView实例
。
js
var buffer = new ArrayBuffer(8);
ArrayBuffer.isView(buffer) // false
var 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属性
。
构造函数
TypedArray
数组提供9种构造函数,用于生成相应类型的数组实例。
构造函数有多种用法。
TypedArray(buffer [, byteOffset] [, length])
同一个ArrayBuffer对象
上,可以根据不同的数据类型建立多个视图。
js
// 创建一个 8 字符的 ArrayBuffer
var b = new ArrayBuffer(8)
// 创建一个指向 b 的 Int32视图,开始于字节 0, 直到缓冲区末尾
var v1 = new Int32Array(b)
// 创建一个指向 b 的 Uint8视图,开始于字节 2,直接缓冲区末尾
var v2 = new Uint8Array(b, 2)
// 创建一个指向 b 的 Int16视图,开始于字节 2,长度为2
var v3 = new Int16Array(b, 2, 2)
上面的代码在一段长度为8个字符的内存(b)上,生成了3个视图:v1、v2和v3。
视图的构造函数可以接受3个参数:
buffer
(必需):视图对应底层的ArrayBuffer对象
。byteOffset
(可选):视图开始的字节序号,默认从0开始。length
(可选):视图包含的数据个数,默认直到本段内存区域结束。
因此,v1、v2和v3是重叠的:v1[0]
是一个32位整数,指向字节0~字节3
;v2[0]
是一个8位无符号整数,指向字节2
;v3[0]
是一个16位整数,指向字节2~字节3
。只要任何一个视图对内存有所修改,就会在另外两个视图上反映出来。
注意,byteOffset
必须与所要建立的数据类型一致,否则会报错。
js
var buffer = new ArrayBuffer(8);
var i16 = new Int16Array(buffer, 1);
// Uncaught RangeError: start offset of Int16Array should be a multiple of 2
上面的代码中,新生成一个8个字节的ArrayBuffer对象
,然后在这个对象的第一个字节建立带符号的16位整数视图,结果报错。因为,带符号的16位整数需要2个字节,所以byteOffset参数
必须能被2整除。
如果从任意字节开始解读ArrayBuffer对象
,必须使用DataView视图
,因为TypedArray视图
只提供9种固定的解读格式。
TypedArray(length)
视图还可以不通过ArrayBuffer对象
,而是直接分配内存生成。
js
var f64a = new Float64Array(8);
f64a[0] = 10;
f64a[1] = 20;
f64a[2] = f64a[0] + f64a[1];
上面的代码生成一个8个成员的Float64Array数组
(共64字节),然后依次对每个成员赋值。这时,视图构造函数的参数就是成员的个数。可以看到,视图数组的赋值操作与普通数组毫无差异。
TypedArray(typedArray)
TypedArray数组
的构造函数可以接受另一个TypedArray实例
作为参数。
js
var typedArray = new Int8Array(new Uint8Array(4));
上面的代码中,Int8Array构造函数
接受一个Uint8Array实例
作为参数。
注意,此时生成的数组只是复制了参数数组的值,对应的底层内存是不一样的。新建数组会开辟一段新的内存储存数据,不会在原数组的内存之上建立视图。
js
var x = new Int8Array([1, 1]);
var y = new Int8Array(x);
x[0] // 1
y[0] // 1
x[0] = 2;
y[0] // 1
如果想基于同一段内存构造不同的视图,可以使用下面的写法:
js
var x = new Int8Array([1, 1]);
var y = new Int8Array(x.buffer);
x[0] // 1
y[0] // 1
x[0] = 2;
y[0] // 2
TypedArray(arrayLikeObject)
构造函数的参数也可以是一个普通数组,然后直接生成TypedArray实例
。
js
var typedArray = new Uint8Array([1, 2, ,3, 4]);
注意,这时TypedArray视图
会重新开辟内存,不会在原数组的内存上建立视图。上面的代码从一个普通数组生成一个8位无符号整数的TypedArray实例
。
TypedArray数组
也可以转换回普通数组。
js
var typedArray = Array.prototype.slice.call(typedArray);
数组方法
普通数组的操作方法和属性对于TypedArray数组
完全适用。
- 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.findIndxe(predicate, thisArg?)
- TypedArray.prototype.forEach(callbakFn, 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()
关于以上的用法,请参阅数组方法的介绍。
注意,TypedArray数组
没有concat
方法。如果想合并多个TypedArray数组
,可以用下面这个函数。
js
function concatEnate(resultConstructor, ...arrays) {
let totalLength = 0;
for (let arr of arrays) {
totalLength += arr.length;
}
let result = new resultConstructor(totalLength);
let offset = 0;
for (let arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
return result;
}
concatEnate(Uint8Array, Uint8Array.of(1, 2), Uint8Array.of(3, 4)); // Uint8Array [1, 2, 3, 4]
另外,TypedArray数组
与普通数组一样部署了Iterator接口
,所以可以遍历。
js
let ui8 = Uint8Array.of(0, 1, 2);
for (let byte of ui8) {
console.log(byte);
}
// 0
// 1
// 2
字节序
字节序指的是数值在内存中的表示方式。
js
var buffer = new ArrayBuffer(16);
var int32View = new Int32Array(buffer);
for (var i = 0; i < int32View.length; i++) {
int32View[i] = i * 2;
}
上面的代码生成一个16字节的ArrayBuffer对象
,然后在其基础上建立了一个32位整数的视图。由于每个32位整数占据4个字节,所以一共可以写入4个整数,依次为0、2、4、6。如果在这段数据上接着建立一个16位整数的视图,则可以读出完全不一样的结果。
js
var int16View = new Int16Array(buffer);
for (var i = 0; i < int16View.length; i++) {
console.log(`Entry ${i}: ${int16View[i]}`);
}
// Entry 0: 0
// Entry 1: 0
// Entry 2: 2
// Entry 3: 0
// Entry 4: 4
// Entry 5: 0
// Entry 6: 6
// Entry 7: 0
由于每个16位整数占据2个字节,所以整个ArrayBuffer对象
现在分成8段。然后,由于x86体系
的计算机都采用小端字节序
(little endian),相对重要的字节排在后面的内存地址。相对不重要的字节排在前面的内存地址,所以就得到了上面的结果。
比如,一个占据4个字节的十六进制数0x12345678
,决定其大小的最重要的字节是“12”,最不重要的是“78”。小端字节序
将最不重要的字节排在前面,储存哺育就是“78563412”;大端字节序
则完全相反,将最重要的字节排在前面,储存顺序就是“12345678”。目前,个人电脑几乎都是小端字节序
,所以TypedArray数组
内部也采用了小端字节序
读/写数据,或者更准确地说,按照本机操作系统设定的字节序读/写。
这并不意味着大端字节序
不重要。事实上,很多网络设备和特定的操作系统采用的是大端字节序
。这就带来一个严重的问题:如果一段数据是大端字节序,TypedArray数组将无法正确解析,因为它只能处理小端字节序。为了解决这个问题,JavaScript引入了DataView对象
,可以设定字节序,下文会详细介绍。
下面是另一个例子:
js
// 假定某段 buffer 包含了如下字符: [0x02, 0x01, 0x03, 0x07]
var buffer = new ArrayBuffer(4);
var v1 = new Uint8Array(buffer);
v1[0] = 2;
v1[1] = 1;
v1[2] = 3;
v1[3] = 7;
var uInt16View = new Uint16Array(buffer);
// 计算机采用小端字节序
// 所以头两个字节等于258
if (uInt16View[0] === 258) {
console.log('OK'); // 'OK'
}
// 赋值运算
uInt16View[0] = 255; // 字节变为 [0xFF, 0x00, 0x03, 0x07]
uInt16View[0] = 0xff05; // 字节变为 [0x05, 0xFF, 0x03, 0x07]
uInt16View[1] = 0x0210; // 字节变为 [0x05, 0xFF, 0x10, 0x02]
下面的函数可以用于判断当前视图是小端字节序,还是大端字节序。
js
const BIG_ENDIAN = Symbol('BIG_ENDIAN');
const LITTLE_ENDIAN = Symblo('LITTLE_ENDIAN');
function getPlatFormEndianness () {
let arr32 = Uint32Array.of(0x12345678);
let arr8 = Uint8Array(arr32.buffer);
switch ((arr8[0] * 0x1000000) + (arr8[1] * 0x10000) + (arr8[2] * 0x100) + (arr8[3])) {
case 0x12345678:
return BIG_ENDIAN;
case 0x78563412:
return LITTLE_ENDIAN;
default:
thorw new Error('Unknown endianness');
}
}
总之,与普通数组相比,TypedArray数组
的最大优点就是可以直接操作内存,不需要数据类型转换,所以速度快得多。
BYTES_PER_ELEMENT属性
每一种视图的构造函数,都有一个BYTES_PER_ELEMENT属性
,表示这种数据类型占据的字节数。
js
Int8Array.BYTES_PER_ELEMENT // 1
Uint8Array.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
这个属性在TypedArray实例
上也能获取,即有TypedArray.prototype.BYTES_PER_ELEMENT
。
ArrayBuffer与字符串的互相转换
ArrayBuffer
转为字符串,或者字符串转为ArrayBuffer
,有一个前提是,即字符串的编码方式是确定的。假定字符串采用UTF-16编辑(JavaScript的内部编码方式),那么可以自己编写转换函数。
js
// ArrayBuffer 转为字符串,参数为 ArrayBuffer 对象
function ab2str (buf) {
return String.formCharCode.apply(null, new Uint16Array(buf));
}
// 字符串转为ArrayBuffer对象,参数为字符串
function str2ab (str) {
var buf = new ArrayBuffer(str.length * 2) // 每个字符占用2个字节
var bufView = new Uint16Array(buf);
for (var i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i)
}
return buf;
}
溢出
不同的视图类型所能容纳的数值范围是确定的。超出这个范围就会溢出。
TypedArray数组
对于溢出的处理方法是求余值。正向溢出(overflow)
的含义是输入值大于当前数据类型的最大值,最后得到的值就等于当前数据类型的最小值加上余值,再减去1;负向溢出(underflow)
等于当前数据类型的最大值减去余值,再加上1。
js
var uint8 = new Uint8Array(1);
uint[0] = 256;
uint[0] // 0
unit[0] = -1;
uint[0] // 255
上面的例子中,8位无符号整数的数值范围是0到255,超出这个范围就是溢出。256相当于正向溢出1,即余值为1,最后的值等于0(0 + 1 - 1);-1相当于负向溢出,余值也是1,最后的值等于255(255 - 1 + 1)。
下面是8位带符号整数的例子。
js
var int8 = new Int8Array(1);
int8[0] = 128;
int8[0] // -128
int8[0] = -129;
int8[0] = // 127
Uint8ClampedArray视图
的溢出与上面的规则有所不同。负向溢出都等于0,正向溢出都等于255。
js
var uint8c = new Uint8ClampedArray(1);
uint8c[0] = 256;
uint8c[0] // 255
uint8c[0] = -1;
uint8c[0] // 0
TypedArray.prototype.buffer
TypedArray实例
的buffer属性
返回整段内存区域对应的ArrayBuffer对象
。该属性为只读属性。
js
var a = new Float32Array(64);
var b = new Uint8Array(a.buffer);
上面的代码中,a视图对象
和b视图对象
,对应同一个ArrayBuffer对象
,即同一段内存。
TypedArray.prototype.byteLength, TypeArray.prototype.byteOffset
byteLength属性
返回TypedArray数组
占据的内存长度,单位为字节。byteOffset属性
返回TypedArray数组
从底层ArrayBuffer对象
的哪个字节开始。这两个属性都是只读属性。
js
var b = new ArrayBuffer(8);
var v1 = new Int32Array(b);
var v2 = new Uint8Array(b, 2);
var v3 = new Int16Array(b, 2, 2);
v1.byteLength // 8
v2.byteLength // 6
v3.byteLength // 4
v1.byteOffset // 0
v2.byteOffset // 2
v3.byteOffset // 2
TypedArray.prototype.length
length属性
表示TypedArray数组
有多少个成员。注意区分byteLength属性
和length属性
,前者是字节长度,后者是成员长度。
js
var a = new Int16Array(8);
a.length // 8
a.byteLength // 16
TypedArray.prototype.set()
TypedArray数组
的set方法
用于复制数组(普通数组或TypedArray数组
),也就是将一段内存完全复制到另一段内存。
js
var a = new Uint8Array(8);
var b = new Uint8Array(8);
b.set(a);
上面的代码复制a数组的内容到b数组,字是整段内存的复制,比一个个复制成员的那种复制快得多。
set方法
还可以接受第二个参数,表示从b对象
的哪一个成员开始复制a对象
。
js
var a = new Uint16Array(8);
var b = new Uint16Array(10);
b.set(a, 2);
上面的代码中,b数组
比a数组
多两个成员,所以从b[2]
开始复制。
TypedArray.prototype.subarray()
subarray方法
是对于TypedArray数组
的一部分再建立一个新的视图。
js
var a = new Uint16Array(8);
var b = a.subarray(2, 3);
a.byteLength // 16
b.byteLength // 2
subarray方法
的第一个参数是起始的成员序号,第二个参数是结束的成员序号(不含该成员),如果省略第二个参数则包含剩余的全部成员。所以,上面的a.subarray(2, 3)
意味着b
只包含a[2]
一个成员,字符长度为2。
TypedArray.prototype.slice()
TypedArray实例
的slice方法
可以返回一个指定位置的新的TypedArray实例
。
js
let ui8 = Uint8Array(0, 1, 2);
ui8.slice(-1)
// Uint8Array [2]
上面的代码中,ui8
是8位无序号整数数组视图的一个实例。它的slice方法
可以从当前视图中返回一个新的视图实例。
slice方法
的参数表示原数组的具体位置。负值表示逆向计数的位置。
TypedArray.of()
TypedArray数组
的所有构造函数都有一个静态方法of
,用于将参数转为一个TypedArray实例
js
Float32Array.of(0.151, -8, 3.7) // Float32Array [0.151, -8, 3.7]
下面3种方法会生成同样的TypedArray数组
。
js
// 方法一
let tarr = new Uint8Array([1, 2, 3]);
// 方法二
let tarr = new Uint8Array.of(1, 2, 3);
// 方法三
let tarr = new Uint8Array(3);
tarr[0] // 0
tarr[1] // 1
tarr[2] // 2
TypedArray.from()
静态方法from
接受一个可遍历的数据结构(比如数组)作为参数,返回一个基于些结构的TypedArray实例
。
js
Uint16Array.from([0, 1, 2]) // Uint16Array [0, 1, 2]
这个方法还可以将一种TypedArray实例
转为另一种。
js
var ui16 = Uint16Array.from(Uint8Array.of(0, 1, 2));
ui16 instanceof Uint16Array // true
from方法
还可以接受一个函数作为第二个参数,用于对每个元素进行遍历,类似map方法
。
js
Int8Array.of(127, 126, 125).map(x => 2 * x)
// Int8Array [-2, -4, -6]
Int16Array.from(Int8Array.of(127, 126, 125), x => 2 * x)
// Int16Array [254, 252, 250]
上面的例子中,from方法
没有发生溢出,这说明遍历是针对新生成的16位整数数组,而不是原来的8位整数数组。也就是说,from
会将第一个参数指定的TypedArray数组
复制到另一段内存中(占用内存从3字节变为6字节),再进行处理。
以上,摘抄自阮一峰老师的《ES6标准入门》