教程 webassembly tutorial

第 5 章:与 JavaScript 的双向交互

5.1 导出函数与调用方式

WebAssembly 模块可以通过 export 将函数暴露给 JavaScript,从而在 JS 中直接调用这些函数。以 AssemblyScript 或 Rust 为例,都可以通过特定语法将函数导出。

例如,在 AssemblyScript 中:

ts
// AssemblyScript
export function add(a: i32, b: i32): i32 {
  return a + b;
}

然后在 JavaScript 中加载并调用该函数:

js
const wasm = await WebAssembly.instantiateStreaming(fetch('example.wasm'));
console.log(wasm.instance.exports.add(2, 3)); // 输出 5

Rust 中使用 #[wasm_bindgen]:

rust
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

导出的函数会出现在 JavaScript 的 wasm_bindgen 包装器中。

5.2 从 WASM 调用 JS 函数

WebAssembly 模块也可以调用 JavaScript 中定义的函数。这需要在导入对象中显式声明 JS 函数供 WebAssembly 使用。

JavaScript 侧定义函数并传入:

js
const importObject = {
  env: {
    log: (value) => console.log("WASM Log:", value)
  }
};

const wasm = await WebAssembly.instantiateStreaming(fetch('example.wasm'), importObject);

AssemblyScript 中声明外部导入函数:

ts
@external("env", "log")
declare function log(value: i32): void;

export function testLog(): void {
  log(42);
}

这样就可以在 WASM 中调用外部 JS 函数,实现双向通信。

5.3 数据传递与内存管理

WASM 模块与 JavaScript 之间的数据传递需要注意数据类型的兼容性和内存分配方式。基本类型如整数、浮点数可以直接传递,但复杂类型(如字符串、对象)则需要通过共享内存或传递指针的方式。

传递字符串的常见方式:

  • 在 WASM 中申请一段内存用于字符串;
  • 将字符串编码为 UTF-8 写入该内存;
  • 将指针和长度传给 JS;
  • JS 使用 TextDecoder 解码内存内容。

例如在 JavaScript 中读取 WASM 传来的字符串:

js
function getStringFromMemory(ptr, len, memory) {
  const bytes = new Uint8Array(memory.buffer, ptr, len);
  return new TextDecoder('utf8').decode(bytes);
}

内存管理建议:

  • 避免频繁分配和释放内存,可考虑使用对象池;
  • 使用 wasm-bindgen 时,它会自动处理一些复杂的内存操作;
  • 注意使用 __alloc, __free(AssemblyScript)或手动管理内存(裸 WASM)防止内存泄漏。

通过以上方式,WebAssembly 与 JavaScript 实现了高效且安全的双向交互,为构建高性能 Web 应用提供强大支持。