├── ArrayBuffer
│ ├── `ArrayBuffer`对象、`TypedArray`视图和`DataView`视图是 JavaScript 操作二进制数据的一个接口。这些对象早就存在,属于独立的规格(2011 年 2 月发布),ES6 将它们纳入了 ECMAScript 规格,并且增加了新的方法。它们都是以数组的语法处理二进制数据,所以统称为二进制数组。
│ ├── 二进制数组由三类对象组成。
│ │ └─ (1)`ArrayBuffer`对象:代表内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。
│ │ └─ (2)`TypedArray`视图**:共包括 9 种类型的视图,比如`Uint8Array`(无符号 8 位整数)数组视图, `Int16Array`(16 位整数)数组视图, `Float32Array`(32 位浮点数)数组视图等等。
│ │ └─ (3)`DataView`视图**:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。
│ │ └─ 简单说,`ArrayBuffer`对象代表原始的二进制数据,`TypedArray`视图用来读写简单类型的二进制数据,`DataView`视图用来读写复杂类型的二进制数据。
│ │ └─ `TypedArray`视图支持的数据类型一共有 9 种(`DataView`视图支持除`Uint8C`以外的其他 8 种)。
│ │ └─ 很多浏览器操作的 API,用到了二进制数组操作二进制数据,下面是其中的几个。
│ │ └─ - [Canvas](#canvas) - [Fetch API](#fetch-api) - [File API](#file-api) - [WebSockets](#websocket) - [XMLHttpRequest](#ajax)
│ ├── ArrayBuffer 对象
│ │ └─ 概述
│ │ └─ `ArrayBuffer`对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(`TypedArray`视图和`DataView`视图)来读写,视图的作用是以指定格式解读二进制数据。
│ │ └─ `ArrayBuffer`也是一个构造函数,可以分配一段可以存放数据的连续内存区域。
│ │ └─ `ArrayBuffer`构造函数的参数是所需要的内存大小(单位字节)。
│ │ └─ const buf = new ArrayBuffer(32); const dataView = new DataView(buf); dataView.getUint8(0)
│ │ └─ 上面代码对一段 32 字节的内存,建立`DataView`视图,然后以不带符号的 8 位整数格式,从头读取 8 位二进制数据,结果得到 0,因为原始内存的`ArrayBuffer`对象,默认所有位都是 0。
│ │ └─ 另一种`TypedArray`视图,与`DataView`视图的一个区别是,它不是一个构造函数,而是一组构造函数,代表不同的数据格式。
│ │ └─ 由于两个视图对应的是同一段内存,一个视图修改底层内存,会影响到另一个视图。
│ │ └─ `TypedArray`视图的构造函数,除了接受`ArrayBuffer`实例作为参数,还可以接受普通数组作为参数,直接分配内存生成底层的`ArrayBuffer`实例,并同时完成对这段内存的赋值。
│ │ └─ ArrayBuffer.prototype.byteLength
│ │ └─ `ArrayBuffer`实例的`byteLength`属性,返回所分配的内存区域的字节长度。
│ │ └─ 如果要分配的内存区域很大,有可能分配失败(因为没有那么多的连续空余内存),所以有必要检查是否分配成功。
│ │ └─ ArrayBuffer.prototype.slice()
│ │ └─ `ArrayBuffer`实例有一个`slice`方法,允许将内存区域的一部分,拷贝生成一个新的`ArrayBuffer`对象。
│ │ └─ `slice`方法其实包含两步,第一步是先分配一段新内存,第二步是将原来那个`ArrayBuffer`对象拷贝过去。
│ │ └─ `slice`方法接受两个参数,第一个参数表示拷贝开始的字节序号(含该字节),第二个参数表示拷贝截止的字节序号(不含该字节)。如果省略第二个参数,则默认到原`ArrayBuffer`对象的结尾。
│ │ └─ 除了`slice`方法,`ArrayBuffer`对象不提供任何直接读写内存的方法,只允许在其上方建立视图,然后通过视图读写。
│ │ └─ ArrayBuffer.isView()
│ │ └─ `ArrayBuffer`有一个静态方法`isView`,返回一个布尔值,表示参数是否为`ArrayBuffer`的视图实例。这个方法大致相当于判断参数,是否为`TypedArray`实例或`DataView`实例。
│ ├── 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 种构造函数,用来生成相应类型的数组实例。
│ │ └─ (1)TypedArray(buffer, byteOffset=0, length?)
│ │ └─ 视图的构造函数可以接受三个参数:
│ │ └─ - 第一个参数(必需):视图对应的底层`ArrayBuffer`对象。
│ │ └─ - 第二个参数(可选):视图开始的字节序号,默认从 0 开始。
│ │ └─ - 第三个参数(可选):视图包含的数据个数,默认直到本段内存区域结束。
│ │ └─ 注意,`byteOffset`必须与所要建立的数据类型一致,否则会报错。
│ │ └─ 新生成一个 8 个字节的`ArrayBuffer`对象,然后在这个对象的第一个字节,建立带符号的 16 位整数视图,结果报错。因为,带符号的 16 位整数需要两个字节,所以`byteOffset`参数必须能够被 2 整除。
│ │ └─ 如果想从任意字节开始解读`ArrayBuffer`对象,必须使用`DataView`视图,因为`TypedArray`视图只提供 9 种固定的解读格式。
│ │ └─ (2)TypedArray(length)
│ │ └─ 视图还可以不通过`ArrayBuffer`对象,直接分配内存而生成。
│ │ └─ (3)TypedArray(typedArray)
│ │ └─ TypedArray 数组的构造函数,可以接受另一个`TypedArray`实例作为参数。
│ │ └─ 注意,此时生成的新数组,只是复制了参数数组的值,对应的底层内存是不一样的。新数组会开辟一段新的内存储存数据,不会在原数组的内存之上建立视图。
│ │ └─ (4)TypedArray(arrayLikeObject)
│ │ └─ 构造函数的参数也可以是一个普通数组,然后直接生成`TypedArray`实例。`TypedArray`视图会重新开辟内存,不会在原数组的内存上建立视图。
│ │ └─ 数组方法
│ │ └─ 普通数组的操作方法和属性,对 TypedArray 数组完全适用。TypedArray 数组没有`concat`方法。
│ │ └─ TypedArray 数组与普通数组一样,部署了 Iterator 接口,所以可以被遍历。
│ │ └─ 字节序
│ │
│ │ └─ - 负向溢出(underflow):当输入值小于当前数据类型的最小值,结果等于当前数据类型的最大值减去余值的绝对值,再加上 1。
│ │ └─ `Uint8ClampedArray`视图的溢出规则,与上面的规则不同。它规定,凡是发生正向溢出,该值一律等于当前数据类型的最大值,即 255;如果发生负向溢出,该值一律等于当前数据类型的最小值,即 0。
│ │ └─ TypedArray.prototype.buffer
│ │ └─ `TypedArray`实例的`buffer`属性,返回整段内存区域对应的`ArrayBuffer`对象。该属性为只读属性。
│ │ └─ TypedArray.prototype.byteLength,TypedArray.prototype.byteOffset
│ │ └─ `byteLength`属性返回 TypedArray 数组占据的内存长度,单位为字节。`byteOffset`属性返回 TypedArray 数组从底层`ArrayBuffer`对象的哪个字节开始。这两个属性都是只读属性。
│ │ └─ TypedArray.prototype.length
│ │ └─ `length`属性表示 `TypedArray` 数组含有多少个成员。注意将 `length` 属性和 `byteLength` 属性区分,前者是成员长度,后者是字节长度。
│ │ └─ TypedArray.prototype.set()
│ │ └─ TypedArray 数组的`set`方法用于复制数组(普通数组或 TypedArray 数组),也就是将一段内容完全复制到另一段内存。
│ │ └─ `set`方法还可以接受第二个参数,表示从`b`对象的哪一个成员开始复制`a`对象。
│ │ └─ TypedArray.prototype.subarray()
│ │ └─ `subarray`方法是对于 TypedArray 数组的一部分,再建立一个新的视图。
│ │ └─ `subarray`方法的第一个参数是起始的成员序号,第二个参数是结束的成员序号(不含该成员),如果省略则包含剩余的全部成员。所以,上面代码的`a.subarray(2,3)`,意味着 b 只包含`a[2]`一个成员,字节长度为 2。
│ │ └─ TypedArray.prototype.slice()
│ │ └─ TypeArray 实例的`slice`方法,可以返回一个指定位置的新的`TypedArray`实例。
│ │ └─ `slice`方法的参数,表示原数组的具体位置,开始生成新数组。负值表示逆向的位置,即-1 为倒数第一个位置,-2 表示倒数第二个位置,以此类推。
│ │ └─ TypedArray.of()
│ │ └─ TypedArray 数组的所有构造函数,都有一个静态方法`of`,用于将参数转为一个`TypedArray`实例。
│ │ └─ TypedArray.from()
│ │ └─ 静态方法`from`接受一个可遍历的数据结构(比如数组)作为参数,返回一个基于这个结构的`TypedArray`实例。
│ │ └─ `from`方法还可以接受一个函数,作为第二个参数,用来对每个元素进行遍历,功能类似`map`方法。
│ ├── 复合视图
│ │ └─ 由于视图的构造函数可以指定起始位置和长度,所以在同一段内存之中,可以依次存放不同类型的数据,这叫做“复合视图”。
│ ├── DataView 视图
│ │ └─ 如果一段数据包括多种类型(比如服务器传来的 HTTP 数据),这时除了建立`ArrayBuffer`对象的复合视图以外,还可以通过`DataView`视图进行操作。
│ │ └─ `DataView`视图本身也是构造函数,接受一个`ArrayBuffer`对象作为参数,生成视图。 new DataView(ArrayBuffer buffer [, 字节起始位置 [, 长度]]);
│ │ └─ `ArrayBuffer`对象的各种`TypedArray`视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;而`DataView`视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。
│ │ └─ `DataView`实例有以下属性,含义与`TypedArray`实例的同名方法相同。
│ │ └─ - `DataView.prototype.buffer`:返回对应的 ArrayBuffer 对象
│ │ └─ - `DataView.prototype.byteLength`:返回占据的内存字节长度
│ │ └─ - `DataView.prototype.byteOffset`:返回当前视图从对应的 ArrayBuffer 对象的哪个字节开始
│ │ └─ `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`方法的参数都是一个字节序号(不能是负数,否则会报错),表示从哪个字节开始读取。
│ │ └─ 如果一次读取两个或两个以上字节,就必须明确数据的存储方式,到底是小端字节序还是大端字节序。默认情况下,`DataView`的`get`方法使用大端字节序解读数据,如果需要使用小端字节序解读,必须在`get`方法的第二个参数指定`true`。
│ │ └─ 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`表示使用小端字节序写入。
│ ├── 二进制数组的应用
│ │ └─ AJAX
│ │ └─ 传统上,服务器通过 AJAX 操作只能返回文本数据,即`responseType`属性默认为`text`。`XMLHttpRequest`第二版`XHR2`允许服务器返回二进制数据,这时分成两种情况。如果明确知道返回的二进制数据类型,可以把返回类型(`responseType`)设为`arraybuffer`;如果不知道,就设为`blob`。
│ │ └─ Canvas
│ │ └─ 网页`Canvas`元素输出的二进制像素数据,就是 TypedArray 数组。
│ │ └─ 这个视图类型的特点,就是专门针对颜色,把每个字节解读为无符号的 8 位整数,即只能取值 0 ~ 255,而且发生运算的时候自动过滤高位溢出。这为图像处理带来了巨大的方便。
│ │ └─ WebSocket
│ │ └─ `WebSocket`可以通过`ArrayBuffer`,发送或接收二进制数据。
│ │ └─ Fetch API
│ │ └─ Fetch API 取回的数据,就是`ArrayBuffer`对象。
│ │ └─ File API
│ │ └─ 如果知道一个文件的二进制数据类型,也可以将这个文件读取为`ArrayBuffer`对象。
│ ├── SharedArrayBuffer
│ │ └─ JavaScript 是单线程的,Web worker 引入了多线程:主线程用来与用户互动,Worker 线程用来承担计算任务。每个线程的数据都是隔离的,通过`postMessage()`通信。
│ │ └─ 主线程新建了一个 Worker 线程。该线程与主线程之间会有一个通信渠道,主线程通过`w.postMessage`向 Worker 线程发消息,同时通过`message`事件监听 Worker 线程的回应。
│ │ └─ 线程之间的数据交换可以是各种格式,不仅仅是字符串,也可以是二进制数据。这种交换采用的是复制机制,即一个进程将需要分享的数据复制一份,通过`postMessage`方法交给另一个进程。如果数据量比较大,这种通信的效率显然比较低。很容易想到,这时可以留出一块内存区域,由主线程与 Worker 线程共享,两方都可以读写,那么就会大大提高效率,协作起来也会比较简单(不像`postMessage`那么麻烦)。
│ │ └─ 允许 Worker 线程与主线程共享同一块内存。`SharedArrayBuffer`的 API 与`ArrayBuffer`一模一样,唯一的区别是后者无法共享数据。
│ │ └─ 共享内存也可以在 Worker 线程创建,发给主线程。
│ │ └─ `SharedArrayBuffer`与`ArrayBuffer`一样,本身是无法读写的,必须在上面建立视图,然后通过视图读写。
│ ├── Atomics 对象
│ │