# 第 5 章：与 JavaScript 的双向交互

URL: https://caijiao.org/webassembly/practical/05-js-interaction
Source: docs/webassembly/practical/05-js-interaction.md
Description: 本章讲解 WebAssembly 与 JavaScript 的双向交互，包括如何从 WASM 导出函数并在 JS 中调用、如何在 WASM 中调用 JS 函数，以及数据传递与内存管理的核心概念。

## 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 应用提供强大支持。
