JavaScript与二进制数据的恩怨情仇


文章出自个人博客https://knightyun.github.io/2020/03/09/js-binary-data,转载请申明


编程江湖,终日血雨腥风,论及二进制数据,又有多少豪杰谈笑风生,风生水起,水起船高,高深莫测…

不扯远了,想必谈到二进制数据,大家联想到的就会是 1010110110001 或者 00000000 11111111 00000101 这样的数据流;而这武林之中,号称三剑客之一的 JavaScript,在其行走江湖之际(日常开发),可能厮杀(处理)最多的类型就是直观的数字、字符串或者对象等;那么与极少露面的隐士(二进制)狭路相逢之时,它又将作何应对(描述与处理二进制数据)呢?是波澜壮阔,还是全身而退,抑或是力挽狂澜,且听本文中分解。

ArrayBuffer

未曾识得英雄面,只缘身在此山中;先来了解第一个概念,ArrayBuffer 表示的是一个原始二进制数据缓冲区(buffer),长度固定,并且内容是只读的;如果需要执行写操作,那么需要使用 类型化数组TypedArray)或者 数据视图DataView) 来实现;

知己知彼,方可百战百胜;在 JavaScript 中与二进制数据接触最紧密的可能就是 ArrayBuffer 了,之前讲目的是要描述和操作二进制数据,那么就要把这些数据先存放到某个地方,然后才能对其进行操作,这里的 ArrayBuffer 缓冲区就可以被看成这么一种地方;当然,可能最直观的方式就是将其保存到字符串中,如 "101011011",又或者存入数组,如 [1,0,1,0,1,1,0,1],这样确实是方便人类了,但是机器执行的效率也降低了,因为毕竟字符和数组是另外两种基本类型,并且也不是专门为此设计的;所以,就出现了专门为缓冲数据设计的 ArrayBuffer,并通过结合视图来提供一个访问和操作数据的接口;

语法

实例化 ArrayBuffer 构造函数时,只接受一个参数,就是要创建的 Arraybuffer 的大小,单位是字节,不指定的话默认为 0;同时它也提供了一个实例属性 byteLength(只读),实现对当前 ArrayBuffer 字节值的访问;

举例:

var buffer = new ArrayBuffer(3);

console.log(buffer.byteLength); // 3

另外,由于 ArrayBuffer 只是负责创建这么一段数据区域,并没有提供初始化赋值的接口,所以这 n 字节的数据都为空,即都置 0;

方法

由于 ArrayBuffer 构造函数本身是用于创建数据缓冲区,并且数据只读,所以提供的属性和方法也只有少数几个;

.slice()

用于返回一个新的缓冲区,语法为 .slice(start, end),即以当前缓冲区为母本,从索引为 start 的位置开始,到 end 位置结束(end 位置不包含在内),然后复制并返回这一区段的数据;其用法大致与 Array.prototype.slice() 类似,举例说明:

var buffer1 = new ArrayBuffer(5);
var buffer2 = buffer1.slice(0, 3);
var buffer3 = buffer1.slice(2);
var buffer4 = buffer1.slice(1, -1);

console.log(buffer2.byteLength); // 3
console.log(buffer3.byteLength); // 3
console.log(buffer4.byteLength); // 3

ArrayBuffer.isView()

该方法用来判断所提供的参数值是否是一种 ArrayBuffer 视图,比如类型化数组(TypedArray)和数据视图(DataView),例如:

console.log(ArrayBuffer.isView());                   // false
console.log(ArrayBuffer.isView([1, 2, 3]));          // false
console.log(ArrayBuffer.isView(new ArrayBuffer(3))); // false

console.log(ArrayBuffer.isView(new Int8Array()));                 // true
console.log(ArrayBuffer.isView(new Uint32Array(3)));              // true
console.log(ArrayBuffer.isView(new DataView(new ArrayBuffer()))); // true

类型化数组(TypedArray)

概述

工欲善其事必先利其器;前面提到操作 ArrayBuffer 创建的数据缓冲区需要使用视图(view)实现,类型化数组就是这么一个描述二进制数据缓冲区(buffer)的视图(view),这个视图是一个 类数组。另外,不存在 TypedArray() 这个构造函数,它指的是一类数组,因此它有多种实现,即多个类型化数组构造器函数;可以姑且理解为 水果 之于 苹果香蕉,水果指的是一类食物,都知道并不存在名为 水果 的一种具体食物,但是 苹果香蕉 是具体存在的;

有效的类型如下:

Int8Array(); // 8 位二进制有符号整数
Uint8Array(); // 8 位无符号整数(超出范围后从另一边界循环)
Uint8ClampedArray(); // 8 位无符号整数(超出范围后为边界值)
Int16Array(); // 16 位二进制有符号整数
Uint16Array(); // 16 位无符号整数signed
Int32Array(); // 32 位二进制有符号整数
Uint32Array(); // 32 位无符号整数
Float32Array(); // 32 位 IEEE 浮点数(7 位有效数字,如 1.1234567)
Float64Array(); // 64 位 IEEE 浮点数(16 有效数字,如 1.123...15)
BigInt64Array(); // 64 位二进制有符号整数
BigUint64Array(); // 64 位无符号整数

语法:

万法不离其宗,一招一式都有迹可循;后面就都以 Int8Array() 为例进行说明,以下代码展示了可以传入的参数类型:

new Int8Array(); // ES2017 中新增
new Int8Array(length); 
new Int8Array(typedArray); 
new Int8Array(object); 
new Int8Array(buffer [, byteOffset [, length]]); 

无参数

最好的招式是没有招式;实例化构造函数时不传入任何参数,则返回一个空的类型化数组:

var i8 = new Int8Array();

console.log(i8); // Int8Array []
console.log(i8.length); // 0
console.log(i8.byteLength); // 0
console.log(i8.byteOffset); // 0

length

一寸长一寸强;传入一个数字类型的参数,表示申明类型化数组中元素的个数:

var i8 = new Int8Array(3);
var _i8 = new Int8Array('3'); // 字符串会先被转换成数字

console.log(i8); // Int8Array(3) [0, 0, 0]
console.log(_i8); // Int8Array(3) [0, 0, 0]
console.log(i8.length); // 3
console.log(i8.byteLength); // 3
console.log(i8.byteOffset); // 0

typedArray

好招不怕效仿;当传入的一个参数同样是一个类型化数组时,则返回一个原类型数组的拷贝(不是引用):

var i8 = new Int8Array(3);
var _i8 = new Int8Array(i8);

console.log(i8 == _i8); // false
console.log(i8 === _i8); // false
console.log(_i8); // Int8Array(3) [0, 0, 0]

object

海纳百川有容乃大;使用该参数时类似于用 TypedArray.prototype.from() 方法创建的类型数组,同时该方法也和 Array.from() 方法类似,即这个 object 参数是一个类数组的对象,或者是可迭代的对象;举例:

// 数组
var i81 = new Int8Array([1, 2, 3]);
console.log(i81);
// Int8Array(3) [1, 2, 3]

// 等价的操作
var i81 = Int8Array.from([1, 2, 3]);
// Int8Array(3) [1, 2, 3]

// 类数组
var i82 = new Int8Array({
   
    0: 1,
    1: 2,
    2: 3,
    length: 3
});
console.log(i82);
// Int8Array(3) [1, 2, 3]

// 可迭代对象
var i83 = new Int8Array(new Set([1, 2, 3]));
console.log(i83);
// Int8Array(3) [1, 2, 3]

buffer, byteOffset, length

众人拾柴火焰高;该构造函数也支持同时提供三个参数,第一个 buffer 指的是数组缓冲区,是 ArrayBuffer 的实例,同时也是 Int8Array.prototype.buffer 这个属性的值;butyOffset 指的是元素的偏移值,表示从数组中第几个元素开始读取,默认是 0,也就是数组的第一个元素;length 指的是在设置了偏移值后,要读取的元素长度,默认是整个数组的长度;举例说明:

var buf = new Int8Array([1,2,3,4,5]).buffer;
var i8 = new Int8Array(buf, 1, 2);

console.log(i8);
// Int8Array(2) [2, 3]

也就是让申明的类型化数组在提供的 buffer 的基础上,从它的索引为 1 的元素(第二个元素)开始读取,然后向后读取 2 个元素;该操作一般用于对缓冲区数据的截取;

类型差异

存在即合理;根据前面的介绍,TypedArray 定义了多种类型,如 Int8Array, Uint8Array, Int16Array 等,这样做也是为了适应不同的应用场景,接下来大致了解一下几个典型的类型化数组之间的区别;

有无符号

Int8ArrayUint8Array 为例,其实 有符号 的意思是数组中的元素可以存在符号,即可以是负数;因此 无符号 的意思就是元素只能是非负数,举例:

var i8 = new Int8Array([1, 2, 3]);
var _i8 = new Int8Array([-
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑝琦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值