零基础学习JS--基础篇--JavaScript类型化数组

JavaScript 类型化数组是一种类似数组的对象,并提供了一种用于在内存缓冲中访问原始二进制数据的机制。

引入类型化数组并非是为了取代 JavaScript 中数组的任何一种功能。相反,它为开发者提供了一个操作二进制数据的接口。这在操作与平台相关的特性时会很有用,例如:音频视频编辑和访问 WebSocket 原始数据等。JavaScript 类型化数组中的每一个元素都是以某种格式表示的原始二进制值,JavaScript 支持从 8 位整数到 64 位浮点数的多种二进制格式。

类型化数组拥有许多与数组相同的方法,语义也相似。但是,类型化数组并不是普通数组,因为在类型化数组上调用 Array.isArray() 会返回 false。此外,并不是所有可用于普通数组的方法都能被类型化数组所支持(如 push 和 pop)。

为了最大程度的灵活性和效率,JavaScript 将类型化数组的实现拆分为缓冲和视图两部分缓冲是一种表示了数据块的对象,它没有格式可言,也没有提供访问其内容的机制。为了访问缓冲中的内存,你需要使用视图。视图提供了上下文——即数据类型、起始偏移量和元素数量。

缓冲

有两种类型的缓冲:ArrayBuffer 和 SharedArrayBuffer。它们都是内存块的低级表示。它们名字中都含有“array”,但是它们与数组并没有太多关系——你不能直接读写它们。相反,缓冲是通用的对象,它们只包含原始数据。为了访问缓冲所表示的内存,你需要使用视图。

缓冲支持以下操作:

  • 分配:创建一个新的缓冲时,会分配一个新的内存块,并且初始化为 0
  • 复制:使用 slice() 方法,你可以高效地复制缓冲中的一部分数据,而不需要创建视图来手动复制每一个字节。
  • 转移:使用 transfer() 和 transferToFixedLength() 方法,可以将内存块的所有权转移给一个新的缓冲对象。若你想要在不同的执行上下文间转移数据,又不想复制,这些方法就很有用。转移后,原始缓冲将不再可用。SharedArrayBuffer 不能被转移(因为缓冲已经被所有执行上下文共享)。
  • 调整大小:使用 resize() 方法,可以调整内存块的大小(只要不超过预设的 maxByteLength 限制)。SharedArrayBuffer 只能增长,不能缩小。

ArrayBuffer 与 SharedArrayBuffer 之间的区别:
ArrayBuffer 在同一时刻只能所属于单个执行上下文。如果你将 ArrayBuffer 传递给另一个执行上下文,它会被转移,原始的 ArrayBuffer 将不再可用。这确保了同一时刻只有一个执行上下文可以访问内存。SharedArrayBuffer 在传递给另一个执行上下文时不会被转移,因此可以被多个执行上下文同时访问。当多个线程同时访问同一内存块时,可能会出现竞争条件,这时候 Atomics 方法就很有用了。

视图

目前主要有两种视图:类型化数组视图和 DataView。

类型化数组提供了实用方法,可以方便地转换二进制数据。DataView 更底层,可以精确控制数据的访问方式。使用这两种视图读写数据的方式是非常不同的。

两种视图都会使 ArrayBuffer.isView() 返回 true。它们都有以下属性:

buffer:视图所引用的底层缓冲。

byteOffset:视图相对于缓冲起始位置的偏移量(以字节为单位)。

byteLength:视图的长度(以字节为单位)。

两者的构造函数都接受上述三个分离的参数。类型化数组构造函数还接受 length 作为元素数量,而不是字节长度。

类型化数组视图:

类型化数组视图有自描述的名称,并且提供了所有常见数值类型的视图,如 Int8Uint32 和 Float64 等等。还有一种特殊的类型化数组视图,Uint8ClampedArray,它会将值钳制(clamp)到 0 到 255 之间,这在 Canvas 数据处理等场景中很有用。

类型值范围字节数描述对应的 Web IDL 类型等效的 C 类型
Int8Array-128~12718 位有符号整数(补码)byteint8_t
Uint8Array0~25518 位无符号整数octetuint8_t
Uint8ClampedArray0~25518 位无符号整数(值会被裁剪)octetuint8_t
Int16Array-32768~32767216 位有符号整数(补码)shortint16_t
Uint16Array0~65535216 位无符号整数unsigned shortuint16_t
Int32Array-2147483648~2147483647432 位有符号整数(补码)longint32_t
Uint32Array0~4294967295432 位无符号整数unsigned longuint32_t
Float32Array-3.4E38~3.4E38 以及 1.2E-38(最小正数)432 位 IEEE 浮点数(7 位有效数字,例如 1.123456unrestricted floatfloat
Float64Array-1.8E308~1.8E308 以及 5E-324(最小正数)864 位 IEEE 浮点数(16 位有效数字,例如 1.123...15unrestricted doubledouble
BigInt64Array-263~263 - 1864 位有符号整数(补码)bigintint64_t (signed long long)
BigUint64Array (en-US)0~264 - 1864 位无符号整数bigint

所有类型化数组类型都有相同的方法与属性,这些方法与属性由 TypedArray 类定义。它们的唯一区别在于底层数据类型和字节数的不同。这在值编码与标准化中有详细讨论。

类型化数组原则上是固定长度的,因此并不存在可以改变数组长度的方法,如 poppushshiftsplice 和 unshift。此外,flat 也不可用,因为类型化数组无法嵌套;concat 和 flatMap 则是由于应用场景不大,亦不可用。由于 splice 不可用,因此 toSpliced 也不可用。所有其他数组方法都是 Array 和 TypedArray 共享的。

另一方面,类型化数组有额外的 set 和 subarray 方法,可以用来优化“同缓冲多视图”的场景。set() 方法允许你使用一个数组或类型化数组的数据,来对另一个类型化数组的多个索引同时进行设置。如果两个类型化数组共享同一个底层缓冲,那么这个操作可能会更加高效,因为这是一个快速的内存移动。subarray() 方法创建一个新的类型化数组视图,它引用与原始类型化数组相同的缓冲,但是范围更窄。

在不改变底层缓冲的前提下,无法直接改变类型化数组的长度。但是,当类型化数组的底层是可调整大小的缓冲,且没有固定的 byteLength 时,它就会跟踪长度,即它的长度会随着缓冲的大小而自动调整。详情请参阅底层为可变大小缓冲时的行为。

类似于普通数组,你可以使用方括号表示法来访问类型化数组的元素。底层缓冲中对应的字节会被解析为一个数字。任何使用数字(或数字的字符串表示,因为数字总是在访问属性时被转换为字符串)的属性访问都会被类型化数组代理——它们永远不会与对象本身交互。这意味着,例如:

  • 超越边界索引的访问总是返回 undefined,而不会实际访问对象上的属性。
  • 任何尝试写入超越边界的属性的行为都不会生效:它既不会抛出错误,也不会改变缓冲或类型化数组。
  • 类型化数组的索引看起来是可配置的和可写的,但是任何改变它们的属性的尝试都会失败。

例如:

const uint8 = new Uint8Array([1, 2, 3]);
console.log(uint8[0]); // 1

// 仅用作示例。不要在生产代码中使用。
uint8[-1] = 0;
uint8[2.5] = 0;
uint8[NaN] = 0;
console.log(Object.keys(uint8)); // ["0", "1", "2"]
console.log(uint8[NaN]); // undefined

// 非数字访问仍然有效
uint8[true] = 0;
console.log(uint8[true]); // 0

Object.freeze(uint8); // TypeError:无法冻结非空缓冲的视图

DataView:

DataView 是一种底层接口,它提供可以操作缓冲中任意数据的 getter/setter API。这对操作不同类型数据的场景很有帮助,例如:类型化数组视图都是运行在本地字节序模式(参考字节序),可以通过使用 DataView 来控制字节序。默认是大端字节序(big-endian),但可以调用 getter/setter 方法改为小端字节序(little-endian)。

DataView 不需要对齐,多字节读写可以从任意指定的偏移量开始。setter 方法也是如此。

下面的例子使用 DataView 来获取任意数字的二进制表示:

function toBinary(
  x,
  { type = "Float64", littleEndian = false, separator = " ", radix = 16 } = {},
) {
  const bytesNeeded = globalThis[`${type}Array`].BYTES_PER_ELEMENT;
  const dv = new DataView(new ArrayBuffer(bytesNeeded));
  dv[`set${type}`](0, x, littleEndian);
  const bytes = Array.from({ length: bytesNeeded }, (_, i) =>
    dv
      .getUint8(i)
      .toString(radix)
      .padStart(8 / Math.log2(radix), "0"),
  );
  return bytes.join(separator);
}

console.log(toBinary(1.1)); // 3f f1 99 99 99 99 99 9a
console.log(toBinary(1.1, { littleEndian: true })); // 9a 99 99 99 99 99 f1 3f
console.log(toBinary(20, { type: "Int8", radix: 2 })); // 00010100

使用类型化数组的Web API

下面是一些使用类型化数组的 API 示例,部分API并不完整。

FileReader.prototype.readAsArrayBuffer():FileReader.prototype.readAsArrayBuffer() 读取对应的 Blob 或 File 的内容

XMLHttpRequest.prototype.send():XMLHttpRequest 实例的 send() 方法现在支持使用类型化数组和 ArrayBuffer 对象作为参数。

ImageData.data:是一个 Uint8ClampedArray 对象,用来描述包含按照 RGBA 序列的颜色数据的一维数组,其值的范围在 0 到 255 之间。

这里API不过多介绍,也不给出示例,大家可以点击链接进行详细的学习。

示例

使用缓冲和视图:

首先,我们需要创建一个 16 字节固定长度的缓冲:

const buffer = new ArrayBuffer(16);

现在我们有了一段初始化为 0 的内存,目前还做不了什么太多操作。让我们确认一下数据的字节长度:

if (buffer.byteLength === 16) {
  console.log("是 16 字节。");
} else {
  console.log("大小有问题!");
}

在实际开始操作这个缓冲之前,还需要一个视图。现在,我们就创建一个视图,该视图将把缓冲内的数据视为一个 32 位有符号整数数组:

const int32View = new Int32Array(buffer);

现在我们可以像普通数组一样访问该数组中的元素:

for (let i = 0; i < int32View.length; i++) {
  int32View[i] = i * 2;
}

该代码会将数组以 024 和 6 填充(一共 4 个 4 字节元素,所以总长度为 16 字节)。

相同数据上的多个视图:

更有意思的是,你可以在同一数据上创建多个视图。例如:基于上文的代码,我们可以像这样继续操作:

const int16View = new Int16Array(buffer);

for (let i = 0; i < int16View.length; i++) {
  console.log(`索引 ${i}:${int16View[i]}`);
}

这里我们创建了一个 16 位整数视图,该视图共享上文的 32 位整数视图的缓冲,然后以 16 位整数打印出缓冲里的数据,这次我们会得到 00204060 这样的输出(假设是小端序编码)。

Int16Array  |   0  |  0   |   2  |  0   |   4  |  0   |   6  |  0   |
Int32Array  |      0      |      2      |      4      |      6      |
ArrayBuffer | 00 00 00 00 | 02 00 00 00 | 04 00 00 00 | 06 00 00 00 |

进一步地:

int16View[0] = 32;
console.log(`32 位数组的索引 0 处数据是:${int32View[0]}`);

这次的输出是 "32 位数组的索引 0 处数据是:32"。也就是,这 2 个数组都是同一数据的以不同格式展示出来的视图。

Int16Array  |  32  |  0   |   2  |  0   |   4  |  0   |   6  |  0   |
Int32Array  |     32      |      2      |      4      |      6      |
ArrayBuffer | 00 02 00 00 | 02 00 00 00 | 04 00 00 00 | 06 00 00 00 |

你可以对任意视图类型进行这样的操作,尽管如果你设置一个整数,然后以浮点数的形式读取它,你可能会得到一个奇怪的结果,因为位的解释方式不同。

const float32View = new Float32Array(buffer);
console.log(float32View[0]); // 4.484155085839415e-44

从缓冲读取文本:

缓冲并不总是表示数字。例如,读取文件可以给你一个文本数据缓冲。你可以使用类型化数组从缓冲中读取这些数据。

下面的代码使用 TextDecoder web API 读取 UTF-8 文本:

const buffer = new ArrayBuffer(8);
const uint8 = new Uint8Array(buffer);
// 我们手动写入数据,假装它已经在缓冲区中了
uint8.set([228, 189, 160, 229, 165, 189]);
const text = new TextDecoder().decode(uint8);
console.log(text); // "你好"

下面的代码使用 String.fromCharCode() 方法读取 UTF-16 文本:

const buffer = new ArrayBuffer(8);
const uint16 = new Uint16Array(buffer);
// 我们手动写入数据,假装它已经在缓冲区中了
uint16.set([0x4f60, 0x597d]);
const text = String.fromCharCode(...uint16);
console.log(text); // "你好"

参见

  • 使用类型化数组实现快速的 Canvas 像素操作
  • 类型化数组:浏览器中的二进制数据
  • 字节序
  • ArrayBuffer
  • DataView
  • TypedArray
  • SharedArrayBuffer

本章节以及以后章节的内容和之前章节相比,基础性较弱,通用性较低,更像是一个新的知识点,从未接触过,也从未了解过,这些知识点需要我们去接触认识,并一点一点学习。

附:以上内容均为个人在MDN网站上学习JS的笔记,若有侵权,将在第一时间删除,若有错误,将在第一时间修改。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/450240.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【性能测试】Jmeter+InfluxDB+Grafana 搭建性能监控平台

一、背景 为什么要搭建性能监控平台&#xff1f; 在用 Jmeter 获取性能测试结果的时候&#xff0c;Jmeter自带的测试报告如下&#xff1a; 这个报告有几个很明显的缺点&#xff1a; 只能自己看&#xff0c;无法实时共享&#xff1b;报告信息的展示比较简陋单一&#xff0c;不…

数据库管理-第159期 Oracle Vector DB AI-10(20240311)

数据库管理159期 2024-03-11 数据库管理-第159期 Oracle Vector DB & AI-10&#xff08;20240311&#xff09;1 其他distance函数2 实例演示使用其他函数寻找最近向量点函数变体简写语法 总结 数据库管理-第159期 Oracle Vector DB & AI-10&#xff08;20240311&#x…

如何理解JavaScript一切皆对象

"JavaScript一切皆对象"的说法&#xff0c;实际上是JavaScript编程语言的一个核心特性和设计理念。在JavaScript中&#xff0c;几乎所有的东西都可以被看作是一个对象&#xff0c;包括基本数据类型&#xff08;如数字、字符串、布尔值&#xff09;和复杂的数据结构&a…

算法耗时通用优化技巧 总结

最近在部署AI相关的算法&#xff0c;并要求减少总耗时&#xff0c;从中总结出的一些比较通用的优化技巧。精髓总结一句话就是&#xff1a;在同一时间尽可能充分利用硬件资源。而怎么尽可能充分利用呢&#xff0c;方式就是多线程并行处理。 1、单线程串行处理数据 假设算法需要…

Linux入门基本指令(1)

✨前言✨ &#x1f4d8; 博客主页&#xff1a;to Keep博客主页 &#x1f646;欢迎关注&#xff0c;&#x1f44d;点赞&#xff0c;&#x1f4dd;留言评论 ⏳首发时间&#xff1a;2024年3月12日 &#x1f4e8; 博主码云地址&#xff1a;渣渣C &#x1f4d5;参考书籍&#xff1a;…

Java开发从入门到精通(一):Java的项目案例

Java大数据开发和安全开发 Java 案例练习案例一:买飞机票案例二:开发验证码案例三:评委打分案例四:数字加密案例五:数组拷贝案例六: 抢红包案例七:找素数案例八:模拟双色球[拓展案例] Java 案例练习 案例一:买飞机票 分析: 方法是需要接收数据?需要接收机票原价、当前月份、舱…

【更新】cyの破三之旅(20240313~20240421)

序言 在过去一个多月里&#xff0c;我又如期搞砸一些事。 有些事不去做会遗憾&#xff0c;做完也很自责。 经年如此&#xff0c;已经习惯了。 但是这次破三&#xff0c;我一定不会再搞砸了。&#xff08;FLAG已立&#xff0c;完篇收FLAG&#xff09; PS&#xff1a;真的勇…

opengl 学习(三)-----着色器

着色器 分类demo效果解析教程 分类 OPengl C demo #include "glad/glad.h" #include "glfw3.h" #include <iostream> #include <cmath> #include <vector>#include <string> #include <fstream> #include <sstream>…

【Python数据结构与判断3/7】储存多个数据的元组

目录 导入 数据结构 元组 索引 in运算 ​编辑 Debug 总结 导入 我们之前学习的变量可以存储1个数据&#xff0c;那当我们需要存储10个数据的时候该怎么办呢&#xff1f;写10个变量可以解决这个问题&#xff0c;但是太冗长。 数据结构 我们也可以如第2张图所示&#xff0…

Accelerating Performance of GPU-based Workloads Using CXL——论文泛读

FlexScience 2023 Paper CXL论文阅读笔记整理 问题 跨多GPU系统运行的高性能计算&#xff08;HPC&#xff09;工作负载&#xff0c;如科学模拟和深度学习&#xff0c;是内存和数据密集型的&#xff0c;依赖于主机内存来补充其有限的板载高带宽内存&#xff08;HBM&#xff09…

【代码随想录 | 数组 02】移除元素

文章目录 2.移除元素2.1题目2.1思路2.2.1暴力解法2.2.2双指针法 23总结 2.移除元素 27.移除元素_力扣链接 2.1题目 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&am…

Kubernetes | 起源 | 组件详解

起源 起源&#xff1a; Kubernetes&#xff08;常简称为K8s&#xff09;起源于Google内部的Borg项目&#xff0c;是一个开源的容器编排引擎&#xff0c;于2014年首次对外发布。 Google Borg Google Borg 是 Google 内部开发和使用的大规模集群管理系统&#xff0c;用于管理和运…

Visual grounding-视觉定位任务介绍

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

网络通信另个角度的认识(进程间通信),端口号(为什么要有,和pid的关系,如何封装,和进程的定位原理+对应关系)客户端如何拿到服务端的port

目录 另一个角度认识网络通信 端口号 引入 -- 为什么要有端口号 问题 解决 端口号和pid 举例 介绍 分类 知名端口 注册端口 动态端口 客户端如何知道服务端的端口号 封装端口号 定位原理 进程和端口号的对应关系 数据如何被上层进程读到 另一个角度认识网络…

Node.js安装 (WIndows版本)

一. 下载地址 Node.js 安装包及源码下载地址为&#xff1a;https://nodejs.org/en/download 二. 具体安装步骤 下载对应msi文件后&#xff0c;双击下载后的安装包 点击next&#xff0c;进入下一步 选择安装目录&#xff0c;建议修改到非C盘目录 选择安装环境 点击 I…

docker启动时环境变量不生效(docker打包成镜像后环境变量失效)

前言 因项目需要多处部署&#xff0c;为了部署的方便&#xff0c;于是准备将项目环境打包成docker镜像以便于部署。mq、mysql这些在仓库中都有现成的镜像&#xff0c;虽然java和nginx的也都有&#xff0c;但是不知道当时是怎么想的&#xff0c;就不想搞太多镜像&#xff0c;也…

力扣大厂热门面试算法题 15-17

15. 三数之和&#xff0c;16. 最接近的三数之和&#xff0c;17. 电话号码的字母组合&#xff0c;每题做详细思路梳理&#xff0c;配套Python&Java双语代码&#xff0c; 2024.03.11 可通过leetcode所有测试用例。 目录 15. 三数之和 解题思路 完整代码 Java Python ​…

29.基于SSM实现的少儿编程网上报名系统(项目 + 论文PPT)

项目介绍 随着计算机的不断发展&#xff0c;已经融入到我们生活工作的每一个领域中&#xff0c;给我们的生活工作带来了很多的便利&#xff0c;因此&#xff0c;希望可以通过该系统的开发也能使少儿编程网上报名系统实现信息化管理&#xff0c;减轻人的负担&#xff0c;提高工作…

申请选择商家转账到零钱流程怎样的

商家转账到零钱是什么&#xff1f; 【商家转账到零钱】可以说是【企业付款到零钱】的升级版&#xff0c;商家转账到零钱可以为商户提供同时向多个用户微信零钱转账的能力&#xff0c;支持分销返佣、佣金报酬、企业报销、企业补贴、服务款项、采购货款等自动向用户转账的场景。…

伦敦金交易市场的参与者有哪些?

伦敦金交易市场&#xff0c;作为全球最大的黄金交易市场之一&#xff0c;吸引了众多的参与者。这些参与者来自不同的背景和角色&#xff0c;每个人都在这个市场中发挥着重要的作用。 首先&#xff0c;我们不得不提到的是市场的核心参与者——交易商。交易商是市场上最活跃的群体…
最新文章