加载并运行 WebAssembly 代码

要在 JavaScript 中使用 WebAssembly,您首先需要将模块加载到内存中,然后再进行编译/实例化。本文档提供了用于获取 WebAssembly 字节码的各种机制的参考,以及如何编译/实例化然后运行它。

有哪些选项?

WebAssembly 尚未与 <script type='module'>import 语句集成,因此没有办法让浏览器使用 import 来为您获取模块。

较旧的 WebAssembly.compile/WebAssembly.instantiate 方法要求您在获取原始字节后创建一个包含 WebAssembly 模块二进制文件的 ArrayBuffer,然后对其进行编译/实例化。这类似于 new Function(string),只是我们将字符串(JavaScript 源代码)替换为字节数组(WebAssembly 源代码)。

较新的 WebAssembly.compileStreaming/WebAssembly.instantiateStreaming 方法效率更高——它们直接在来自网络的原始字节流上执行操作,从而省去了 ArrayBuffer 步骤。

那么,我们如何将这些字节加载到 ArrayBuffer 中并进行编译呢?接下来的几部分将对此进行解释。

使用 Fetch

Fetch 是一个方便的现代 API,用于获取网络资源。

获取 Wasm 模块最快捷、最高效的方法是使用较新的 WebAssembly.instantiateStreaming() 方法,它可以将 fetch() 调用作为第一个参数,并一次性完成模块的获取、编译和实例化,直接访问从服务器流式传输过来的原始字节码。

js
WebAssembly.instantiateStreaming(fetch("simple.wasm"), importObject).then(
  (results) => {
    // Do something with the results!
  },
);

如果我们使用不支持直接流的较旧的 WebAssembly.instantiate() 方法,我们需要额外一步将获取的字节码转换为 ArrayBuffer,如下所示:

js
fetch("module.wasm")
  .then((response) => response.arrayBuffer())
  .then((bytes) => WebAssembly.instantiate(bytes, importObject))
  .then((results) => {
    // Do something with the results!
  });

关于 instantiate() 重载的说明

WebAssembly.instantiate() 函数有两种重载形式——上面展示的一种接受要编译的字节码作为参数,并返回一个 Promise,该 Promise 解析为一个对象,其中包含编译后的模块对象和它的一个实例化实例。该对象看起来像这样:

js
({
  module: Module, // The newly compiled WebAssembly.Module object,
  instance: Instance, // A new WebAssembly.Instance of the module object
});

注意:通常我们只关心实例,但为了方便缓存模块、通过 postMessage() 与其他 Worker 或窗口共享,或者创建更多实例,保留模块对象是很有用的。

注意:第二种重载形式接受一个 WebAssembly.Module 对象作为参数,并直接返回一个包含实例对象的 Promise。请参阅 Second overload example

运行您的 WebAssembly 代码

一旦您在 JavaScript 中获得了 WebAssembly 实例,就可以开始使用它通过 WebAssembly.Instance.exports 属性导出的功能。您的代码可能看起来像这样:

js
WebAssembly.instantiateStreaming(fetch("myModule.wasm"), importObject).then(
  (obj) => {
    // Call an exported function:
    obj.instance.exports.exported_func();

    // or access the buffer contents of an exported memory:
    const dv = new DataView(obj.instance.exports.memory.buffer);

    // or access the elements of an exported table:
    const table = obj.instance.exports.table;
    console.log(table.get(0)());
  },
);

注意:有关 WebAssembly 模块如何导出功能的更多信息,请阅读 使用 WebAssembly JavaScript API理解 WebAssembly 文本格式

使用 XMLHttpRequest

XMLHttpRequest 比 Fetch 稍旧,但仍然可以很好地用于获取类型化数组。同样,假设我们的模块名为 simple.wasm

  1. 创建一个新的 XMLHttpRequest() 实例,并使用其 open() 方法打开一个请求,将请求方法设置为 GET,并声明要获取的文件路径。
  2. 关键部分是使用 responseType 属性将响应类型设置为 'arraybuffer'
  3. 接下来,使用 XMLHttpRequest.send() 发送请求。
  4. 然后,我们使用 load 事件处理程序在响应下载完成后调用一个函数——在该函数中,我们从 response 属性获取 ArrayBuffer,然后将其馈送到我们的 WebAssembly.instantiate() 方法中,就像我们使用 Fetch 时一样。

最终代码如下:

js
const request = new XMLHttpRequest();
request.open("GET", "simple.wasm");
request.responseType = "arraybuffer";
request.send();

request.onload = () => {
  const bytes = request.response;
  WebAssembly.instantiate(bytes, importObject).then((results) => {
    results.instance.exports.exported_func();
  });
};

注意:您可以在 xhr-wasm.html 中看到此示例的实际应用。