作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343
邮箱 :291148484@163.com
本文地址:https://blog.csdn.net/qq_28550263/article/details/130879208
【介绍】:本文介绍 WebAssembly 的 JavaScript API。
目 录
- 1. 概述
- 2. WebAssembly JavaScript API 详解
- 2.1 API 概述
- 2.2 WebAssembly API 中的普通函数
- 2.3 WebAssembly API 中的构造函数
- 2.3.1 Module() 构造函数 与 Module 对象
- WebAssembly.Module 对象的构造函数
- Module 对象解析
- customSections 方法
- exports 方法
- imports 方法
- 2.3.2 Global() 构造函数 与 Global 对象
- 2.3.3 Instance() 构造函数 与 Instance 对象
- 2.3.4 Memory() 构造函数 与 Memory 对象
- 2.3.5 Table() 构造函数 与 Table 对象
- 2.3.6 RuntimeError() 构造函数 与 RuntimeError 对象
- 2.3.7 CompileError() 构造函数 与 CompileError 对象
- 2.3.8 LinkError() 构造函数 与 LinkError 对象
2.3.9 MDN意淫:Exception() 构造函数 与 Exception 对象2.3.10 MDN意淫:Tag() 构造函数 与 Tag 对象
1. 概述
1.1 引言
在之前的文章中我们讲解过将某些语言(如 Rust、 C++ 等)编译成 WebAssembly ,也简单讲解过 如何加载和运行 WebAssembly 代码 。
当加载了一个 .wasm 模块 之后,你就想要去使用它。WebAssembly 的 JavaScript API, 就为我们提供了这样的交互能力。 本文在前面已经对 WebAssembly 有一定的使用基础上,争取对 WebAssembly 的 JavaScript API 有全面把握,以为应用自如打好基础。
1.2 为什么需要用于 WebAssembly 的 JavaScript API
WebAssembly 是一种可移植、高性能的二进制格式,可以在现代 Web 浏览器中运行。虽然 WebAssembly 可以直接由编译器生成并在浏览器中运行,但为了能够与 JavaScript 交互和利用 Web 平台的功能,WebAssembly 需要使用 JavaScript API,比如用于操作 DOM,等等。
归纳起来,WebAssembly 目前仍然需要使用 JavaScript API 的主要原因有以下几点:
-
当前,WebAssembly 还没有直接访问浏览器 API 的能力。WebAssembly 本身只是一种二进制格式,没有直接访问浏览器 DOM、网络请求、图形渲染 等能力。通过 JavaScript API,WebAssembly 可以与 JavaScript 代码进行交互,并利用 JavaScript 来访问浏览器提供的功能。
-
JavaScript API 提供了底层数据传输和交互的机制。WebAssembly 与 JavaScript 之间需要进行数据的传递和交互。JavaScript API 提供了一系列的函数和对象,用于在 WebAssembly 模块和 JavaScript 之间传递数据、调用函数、导入导出功能等。
1.3 ★ 浏览器 和 NodeJS 环境加载 wasm 模块 差异
本文后续介绍各个 API 中涉及大量的代码示例,这些示例都是假定在浏览器中使用的。实际上在实际开发中,也经常应用于 NodeJS 环境,这里会有一些不同。在这一小节我们提前把这里的问题讲清楚,后面则不再对该问题进行赘述。
1.3.1 浏览器环境下
浏览器环境下我们使用 Fetch API 起 HTTP 请求来获取 WebAssembly 模块,因为:
-
WebAssembly 模块 通常是作为 独立的二进制文件存在,而不是内联在 HTML 文件中。因此,需要通过网络从服务器获取模块文件。
-
WebAssembly 模块可能具有较大的文件大小,使用 HTTP 请求可以利用浏览器的缓存机制,减少模块的重复下载。
-
Fetch API 提供了一种方便的方式来异步获取资源。它支持 Promise 和 async/await 语法,使得处理异步操作更加简洁和可读。使用 Fetch API 发起请求可以通过设置请求头、处理错误和响应等进行更灵活的控制。
总之,使用 Fetch API 发起 HTTP 请求来获取 WebAssembly 模块是为了从服务器异步获取模块文件,并能够灵活地处理请求和响应过程。这样可以实现模块的动态加载和更好的控制。
1.3.2 NodeJS 环境下
在 Node.js 环境中,由于没有浏览器的环境和 Fetch API,不能直接使用 fetch
函数来获取 WebAssembly 模块(除非你硬要搭建一个静态文件服务器,再来使用代码请求到模块代码来运行)。
通常情况下在 Node.js 环境中我们使用 fs
模块 来读取 模块文件 并 获取其 二进制数据。
具体的,在 NodeJS 中,你可以参考下面示例给出的方式 加载 WebAssembly 模块:
-
使用
fs.readFileSync
读取 WebAssembly 模块的二进制文件,并使用WebAssembly.compile
编译模块。const fs = require('fs'); const buffer = fs.readFileSync('module.wasm'); const module = new WebAssembly.Module(buffer);
-
使用
WebAssembly.instantiate
或WebAssembly.instantiateStreaming
方法直接实例化 WebAssembly 模块。const fs = require('fs'); const buffer = fs.readFileSync('module.wasm'); const module = new WebAssembly.Module(buffer); WebAssembly.instantiate(module).then(instance => { // ... });
2. WebAssembly JavaScript API 详解
2.1 API 概述
2.1.1 JavaScript 的 WebAssembly 对象是一个命名空间对象
在逐个讲解前,我们一定要先注意,类似于 Math 对象 或者 Intl 对象:
JavaScript 中,WebAssembly 不是一个构造函数(它不是一个函数对象),而是所有 WebAssembly 相关功能的 命名空间。——和大多数全局对象不一样。
一般我们主要通过 WebAssembly 对象上提供的:
- WebAssembly.instantiate() 函数 加载 编译好的 加载 WebAssembly 代码;
- WebAssembly.Memory() 构造函数 创建新的内存实例;
- WebAssembly.Table() 构造函数 创建新的表实例;
- WebAssembly.CompileError() 构造函数 来提供 WebAssembly 中的编译错误信息;
- WebAssembly.LinkError() 构造函数 来提供 WebAssembly 模块实例化期间的错误信息;
- WebAssembly.RuntimeError() 构造函数 来提供 WebAssembly 中的运行错误信息。
2.1.2 API 一览表
普通函数
API | 描述 |
---|---|
instantiate() | 编译和实例化 WebAssembly 代码的主要的 API,它返回一个 Module 及其第一个 Instance 实例。 |
instantiateStreaming() | 直接从流式底层源编译和实例化 WebAssembly 模块,同时返回 Module 及其第一个Instance实例。 |
compile() | 把 WebAssembly 二次代码编译为一个 WebAssembly.Module,不进行实例化。 |
compileStreaming() | 直接从流式底层源代码编译 WebAssembly.Module,将实例化作为一个单独的步骤 |
用作构造器函数
这些函数用于通过 JavaScript 函数的 构造调用 以创建他们表示的同名对象实例,相当于基于类的面向对象编程语言中某个类的构造方法,因此用的都是大写字母开头。不了解的可以参考我的另外一篇博文 https://blog.csdn.net/qq_28550263/article/details/123418894。
API | 描述 | 说明 |
---|---|---|
Global(descriptor , value ) | 创建一个新的 Global 对象实例。 | Global 对象表示一个全局变量实例,可以被 JavaScript 和 importable/exportable 访问 ,跨越一个或多个WebAssembly.Module 实例。他允许被多个 modules 动态连接。 |
Module(bufferSource ) | 创建一个新的 Module 对象实例。 | Module 对象包含已经由浏览器编译的无状态 WebAssembly 代码,可以高效地与 Worker 共享和多次实例化。 |
Memory(memoryDescriptor ) | 创建一个新的 Memory 对象实例。 | Memory 对象是一个可调整大小的ArrayBuffer或SharedArrayBuffer,用于保存 WebAssembly.Instance 访问的原始内存字节。 |
Table(tableDescriptor ) | 创建一个新的 Table 对象实例。 | |
CompileError(message , fileName , lineNumber ) | 创建一个新的 CompileError 对象实例。 | CompileError 对象表示 WebAssembly 解码或验证期间的错误。 |
LinkError(message , options ) | 创建一个新的 LinkError 对象实例。 | LinkError 对象指示模块实例化期间的错误(除了来自start函数的 traps 之外)。 |
RuntimeError(message , options ) | 创建一个新的 RuntimeError 对象实例。 | |
Instance(module , importObject ) | 创建一个新的 Instance 对象实例。 | Instance 对象包含所有 导出的 WebAssembly 函数,这些函数允许从 JavaScript 调用 WebAssembly 代码。 |
Tag(type ) | 创建一个新的 Tag 对象实例。 | |
Exception(tag , payload , options ) | 创建一个新的 Exception 对象实例。 | Exception对象表示从 WebAssembly 抛出到 JavaScript 或 从 JavaScript 抛出到 WebAssembly 异常处理程序的 运行时异常。 |
2.2 WebAssembly API 中的普通函数
2.2.1 instantiate() 函数
该方法从已编译的 WebAssembly 模块创建一个实例。
其语法格式为:
WebAssembly.instantiate(bufferSource, importObject)
其中参数:
bufferSource
:包含 WebAssembly 模块字节码的 ArrayBuffer、TypedArray 或 DataView。importObject
(可选):一个对象,用于传递给 WebAssembly 模块的导入对象。
例如:
// 发起对 WebAssembly 模块的 HTTP 请求
const response = await fetch('module.wasm');
// 将响应转换为 ArrayBuffer
const buffer = await response.arrayBuffer();
// 使用 ArrayBuffer 创建 WebAssembly 模块实例
const module = await WebAssembly.instantiate(buffer);
// 获取 WebAssembly 实例对象
const instance = module.instance;
2.2.2 instantiateStreaming() 函数
该方法通过流式加载已编译的 WebAssembly 模块,并创建一个实例。
其语法格式为:
WebAssembly.instantiateStreaming(source, importObject)
其中参数:
source
:一个表示可通过 HTTP 请求获取 WebAssembly 模块的 URL 字符串或 Response 对象。importObject
(可选):一个对象,用于传递给 WebAssembly 模块的导入对象。
例如:
// 流式加载并实例化 WebAssembly 模块
const module = await WebAssembly.instantiateStreaming(fetch('module.wasm'));
// 获取 WebAssembly 实例对象
const instance = module.instance;
2.2.3 compile() 函数
该方法将 WebAssembly 模块的字节码编译成一个可执行的模块对象。
其语法格式为:
WebAssembly.compile(bufferSource)
其中参数:
bufferSource
:包含 WebAssembly 模块字节码的 ArrayBuffer、TypedArray 或 DataView。
例如:
// 发起对 WebAssembly 模块的 HTTP 请求
const response = await fetch('module.wasm');
// 将响应转换为 ArrayBuffer
const buffer = await response.arrayBuffer();
// 将 ArrayBuffer 编译成可执行的 WebAssembly 模块对象
const module = await WebAssembly.compile(buffer);
2.2.4 compileStreaming() 函数
该方法通过流式加载 WebAssembly 模块的字节码,并将其编译成一个可执行的模块对象。其语法格式为:
WebAssembly.compileStreaming(source)
其中参数:
source
:一个表示可通过 HTTP 请求获取 WebAssembly 模块的 URL 字符串或 Response 对象。
例如:
const module = await WebAssembly.compileStreaming(fetch('module.wasm'));
2.3 WebAssembly API 中的构造函数
2.3.1 Module() 构造函数 与 Module 对象
WebAssembly.Module 对象的构造函数
WebAssembly.Module() 构造函数 用于创建一个 WebAssembly.Module 实例对象。它接受一个 ArrayBuffer 作为参数,并返回一个模块对象,用于后续的实例化操作。
该构造函数的类型签名即下一节 Module 对象的类型签名 中的 new 方法的签名,这里不重复给出。
例如:
fetch('module.wasm')
.then(response => response.arrayBuffer())
.then(buffer => {
const module = new WebAssembly.Module(buffer);
// 使用模块进行实例化等操作
});
Module 对象解析
WebAssembly.Module 对象类型签名为:
var WebAssembly.Module: {
new (bytes: BufferSource): Module;
prototype: Module;
customSections(moduleObject: Module, sectionName: string): ArrayBuffer[];
exports(moduleObject: Module): ModuleExportDescriptor[];
imports(moduleObject: Module): ModuleImportDescriptor[];
}
customSections 方法
该方法用于获取模块中自定义的节(section)。
其中参数:
moduleObject
是一个 WebAssembly.Module 对象,sectionName
是一个字符串,表示要获取的自定义节的名称。
该方法返回一个数组,这个数组包含了指定名称的自定义节的内容。
例如:
const response = await fetch('module.wasm');
// 将响应转换为 ArrayBuffer
const buffer = await response.arrayBuffer();
// 将 ArrayBuffer 编译成可执行的 WebAssembly 模块对象
const module = await WebAssembly.compile(buffer);
const customSections = WebAssembly.Module.customSections(module, 'custom_section_name');
console.log(customSections);
exports 方法
该方法用于获取模块中的导出项(exports)信息。
其中参数:
module
是一个 WebAssembly.Module 对象。
该方法返回一个数组,包含了模块中所有导出项的信息。
例如:
const response = await fetch('module.wasm');
// 将响应转换为 ArrayBuffer
const buffer = await response.arrayBuffer();
// 将 ArrayBuffer 编译成可执行的 WebAssembly 模块对象
const module = await WebAssembly.compile(buffer);
const customSections = WebAssembly.Module.customSections(module, 'custom_section_name');
console.log(customSections);
imports 方法
该方法用于获取模块中的导入项(imports)信息。
其中参数:
module
是一个 WebAssembly.Module 对象。
该方法返回一个数组,包含了模块中所有导入项的信息。
例如:
const response = await fetch('module.wasm');
// 将响应转换为 ArrayBuffer
const buffer = await response.arrayBuffer();
// 将 ArrayBuffer 编译成可执行的 WebAssembly 模块对象
const module = await WebAssembly.compile(buffer);
const imports = WebAssembly.Module.imports(module);
console.log(imports);
2.3.2 Global() 构造函数 与 Global 对象
WebAssembly.Global 对象的构造函数
WebAssembly.Global() 构造函数 用于创建一个 WebAssembly.Global 实例对象。
该构造函数的类型签名即下一节 Global 对象的类型签名 中的 new 方法的签名,这里不重复给出。
Global 对象解析
Global 对象的类型签名为:
var WebAssembly.Global: {
new (descriptor: GlobalDescriptor, v?: any): Global;
prototype: Global;
}
这个对象没有需要解释的其它方法。
2.3.3 Instance() 构造函数 与 Instance 对象
WebAssembly.Instance 对象的构造函数
WebAssembly.Instance() 构造函数 用于创建一个 WebAssembly.Instance 实例对象。它接受一个 WebAssembly.Module 对象和一个可选的导入对象作为参数,并返回一个实例对象,用于调用 WebAssembly 模块中的函数和访问导出的功能。
该构造函数的类型签名即下一节 Instance 对象的类型签名 中的 new 方法的签名,这里不重复给出。
例如:
fetch('module.wasm')
.then(response => response.arrayBuffer())
.then(buffer => {
// 创建 wasm 的 Module 对象实例
const module = new WebAssembly.Module(buffer);
// 创建 wasm 的 Instance 对象实例
const instance = new WebAssembly.Instance(module, { /* imports */ });
// 使用实例调用导出的函数等操作
});
WebAssembly.Instance 对象
Instance 对象的类型签名为:
var WebAssembly.Instance: {
new (module: Module, importObject?: Imports): Instance;
prototype: Instance;
}
这个对象没有需要解释的其它方法。
2.3.4 Memory() 构造函数 与 Memory 对象
WebAssembly.Memory 对象的构造函数
WebAssembly.Memory() 构造函数用于创建一个 WebAssembly.Memory 实例对象。它接受一个描述内存大小的页数作为参数,并返回一个内存对象,用于 WebAssembly 模块中的内存访问操作。
该构造函数的类型签名即下一节 Memory 对象的类型签名 中的 new 方法的签名,这里不重复给出。
例如:
const memory = new WebAssembly.Memory({ initial: 10, maximum
Memory 实例对象的 buffer 属性是一个可调整大小的 ArrayBuffer ,其内存储的是 WebAssembly 实例 所访问内存的原始字节码。
WebAssembly.Memory 对象
Memory 对象的类型签名为:
var WebAssembly.Memory: {
new (descriptor: MemoryDescriptor): Memory;
prototype: Memory;
}
这个对象没有需要解释的其它方法。
2.3.5 Table() 构造函数 与 Table 对象
WebAssembly.Table 对象的构造函数
WebAssembly.Table() 构造函数用于创建一个 WebAssembly.Table 实例对象。Table() 构造函数主要传入的是以下两个参数:
- initial:指定表的初始大小(以元素数量表示),默认为 0。
- element:指定表中每个元素的类型(函数引用类型),默认为 {element: “anyfunc”}。
可以参考该对象类型签名及进行理解。该构造函数的类型签名即下一节 Table 对象的类型签名 中的 new 方法的签名,这里不重复给出。
例如:
// 创建一个初始大小为 10 的 WebAssembly 表,存储任意函数引用类型
const table = new WebAssembly.Table({ initial: 10 });
// 向表中添加函数引用
table.grow(2); // 扩展表的大小为 12
table.set(0, myFunction); // 将 myFunction 设置为索引为 0 的元素
table.set(1, anotherFunction); // 将 anotherFunction 设置为索引为 1 的元素
// 调用表中的函数
table.get(0)(); // 调用索引为 0 的函数
WebAssembly.Table 对象
Table 对象的类型签名为:
type TableKind = "anyfunc" | "externref";
interface TableDescriptor {
element: TableKind;
initial: number;
maximum?: number;
}
var WebAssembly.Table: {
new (descriptor: TableDescriptor, value?: any): Table;
prototype: Table;
}
TableKind
类型表示一个对象,表示表中每个元素的类型,可以是以下值之一:
- “anyfunc”: 任意函数引用类型
- “funcref”: 函数引用类型
这个对象没有需要解释的其它方法。
2.3.6 RuntimeError() 构造函数 与 RuntimeError 对象
WebAssembly.RuntimeError 对象的构造函数
WebAssembly.RuntimeError() 构造函数用于创建一个 WebAssembly.RuntimeError 实例对象。通过构造调用该对象,可以创建 WebAssembly 运行时错误(Runtime Error)。
其中参数:
message
:一个可选的字符串,表示错误消息。
例如:
// 创建一个自定义的 WebAssembly 运行时错误
const error = new WebAssembly.RuntimeError("Custom runtime error occurred.");
throw error; // 抛出错误
WebAssembly.RuntimeError 对象
RuntimeError 对象用于在 WebAssembly 模块执行期间抛出自定义的运行时错误。通过 抛出自定义的 WebAssembly.RuntimeError,你可以在 WebAssembly 模块 的 执行期间捕获并处理特定的运行时错误情况。
该对象的类型签名为:
var WebAssembly.RuntimeError: {
(message?: string): RuntimeError;
new (message?: string): RuntimeError;
prototype: RuntimeError;
}
通过抛出自定义的 WebAssembly.RuntimeError,你可以在 WebAssembly 模块的执行期间 捕获并处理特定的运行时错误情况。
【注】:WebAssembly.RuntimeError 是一个构造函数,用于创建错误对象。它不是 JavaScript 中内置的异常类型,而是用于在 WebAssembly 环境中表示运行时错误的特定对象。
这个对象没有需要解释的其它方法。
2.3.7 CompileError() 构造函数 与 CompileError 对象
WebAssembly.CompileError 对象的构造函数
WebAssembly.CompileError() 构造函数 用来创建一个 WebAssembly.CompileError 实例对象,以实现在编译 WebAssembly 模块时抛出自定义的编译错误。
其中参数:
message
:一个可选的字符串,表示错误消息。
例如:
// 创建一个自定义的 WebAssembly 编译错误
const error = new WebAssembly.CompileError("Custom compile error occurred.");
throw error; // 抛出错误
WebAssembly.CompileError 对象
CompileError 对象的类型签名为:
var WebAssembly.CompileError: {
(message?: string): CompileError;
new (message?: string): CompileError;
prototype: CompileError;
}
通过抛出 自定义的 WebAssembly.CompileError,你可以在编译 WebAssembly 模块时捕获并处理特定的编译错误情况。
【注】:WebAssembly.CompileError不是 JavaScript 中内置的异常类型,而是用于在 WebAssembly 环境中表示编译错误的特定对象。
2.3.8 LinkError() 构造函数 与 LinkError 对象
WebAssembly.LinkError 对象的构造函数
WebAssembly.LinkError() 构造函数 用于创建一个 WebAssembly.LinkError 实例对象,从而在链接 WebAssembly 模块时抛出自定义的链接错误。
其中参数:
message
:一个可选的字符串,表示错误消息。
例如:
// 创建一个自定义的 WebAssembly 链接错误
const error = new WebAssembly.LinkError("Custom link error occurred.");
throw error; // 抛出错误
WebAssembly.LinkError 对象
LinkError 对象的类型签名为:
var WebAssembly.LinkError: {
(message?: string): LinkError;
new (message?: string): LinkError;
prototype: LinkError;
}
通过抛出自定义的 WebAssembly.LinkError,你可以在 链接 WebAssembly 模块时 捕获并处理特定的链接错误情况。
【注】:WebAssembly.LinkError 是一个构造函数,用于创建错误对象。它不是 JavaScript 中内置的异常类型,而是用于在 WebAssembly 环境中表示链接错误的特定对象。
这个对象没有需要解释的其它方法。
2.3.9 MDN意淫:Exception() 构造函数 与 Exception 对象
这个 API 是 MDN 网站上给出的,地址为 https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Tag 以下截图引用时间为 2023/5/29:
不过编程的时候,好像这个API并不能像 MDN 上面介绍的那样使用,并且看起来完全不存在该构造函数。在 typescript 的 lib
中,笔者也没有找到该构造函数的任何类型声明。
是否是新的 API 或者是非标准的 API 不得而知。尤其让人疑惑的是,MDN上面还给出了其被支持的浏览器:
但是似乎 W3C 上同样没有:https://www.w3.org/TR/2022/WD-wasm-js-api-2-20220419/。
最让人疑惑的是,MDN 似乎还做得挺认真的,给出了两个语言的版本,简直是无中生有玩得贼溜。
建议感兴趣的自己在 MDN 的以下地址查看:
- https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Tag:英语
- https://developer.mozilla.org/fr/docs/WebAssembly/JavaScript_interface/Tag 法语
法语版本截图:
2.3.10 MDN意淫:Tag() 构造函数 与 Tag 对象
更让人疑惑的是,除了上一节介绍的 Tag,MDN 似乎还以同样认真的手段,不知道从哪里创造了一个 Exception API,而且也给了两个版本:
英语版,地址:https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Exception
法语版,地址:
https://developer.mozilla.org/fr/docs/WebAssembly/JavaScript_interface/Exception
目前 WebAssembly 还属于新技术,“菜鸟XX” 等国内网站都还没有介绍。W3C 在2022 年给出的 WASM 2 的标准中没有,应该就是没有这两个API了。因此这两个 API 到底是我没找到还是被 MDN 意淫出来的呢,请读者自己判断。