强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

WebAssembly 入门教程 / 08 - JavaScript 集成

08 - JavaScript 集成

WebAssembly 的价值只有与 JavaScript 配合才能完全发挥——Wasm 负算力,JS 负生态。


8.1 模块加载与编译

同步 vs 异步 API

// ❌ 同步编译(阻塞主线程,不推荐用于大型模块)
const module = new WebAssembly.Module(buffer);
const instance = new WebAssembly.Instance(module, imports);

// ✅ 异步编译(推荐)
const module = await WebAssembly.compile(buffer);
const instance = await WebAssembly.instantiate(module, imports);

// ✅✅ 流式编译(最高效,直接从响应流编译)
const { instance } = await WebAssembly.instantiateStreaming(
  fetch('module.wasm'), imports
);

加载方式对比

方式适用场景性能浏览器支持
compile() + instantiate()需要复用编译结果✅ 高全部
instantiate(buffer)简单场景⚠️ 中全部
instantiateStreaming()从网络加载✅✅ 最高现代浏览器
compileStreaming()Worker 间传递✅ 高现代浏览器

预编译与缓存

// 缓存编译好的模块到 IndexedDB
async function cachedCompile(url) {
  const cacheKey = `wasm-module:${url}`;
  const db = await openDB('wasm-cache', 1);
  
  // 尝试从缓存读取
  const cached = await db.get('modules', cacheKey);
  if (cached) {
    return cached;
  }
  
  // 编译并缓存
  const response = await fetch(url);
  const module = await WebAssembly.compileStreaming(response);
  await db.put('modules', module, cacheKey);
  
  return module;
}

// 配合 Cache Storage API
async function cachedCompileWithCache(url) {
  const cache = await caches.open('wasm-modules');
  let response = await cache.match(url);
  
  if (!response) {
    response = await fetch(url);
    cache.put(url, response.clone());
  }
  
  return WebAssembly.compileStreaming(response);
}

8.2 调用 Wasm 函数

基本调用

const { instance } = await WebAssembly.instantiateStreaming(
  fetch('math.wasm'),
  imports
);

// 直接调用导出函数
const result = instance.exports.add(10, 20);
console.log(result);  // 30

// 导出的内存和表
const memory = instance.exports.memory;
const table = instance.exports.table;

处理 i64 类型

// ⚠️ i64 在 JS 中被映射为 BigInt
const { instance } = await WebAssembly.instantiateStreaming(
  fetch('bigint.wasm')
);

// i64 参数和返回值需要使用 BigInt
const result = instance.exports.multiply(BigInt(100), BigInt(200));
console.log(Number(result));  // 20000

// BigInt 和 Number 互转
const bigVal = BigInt(Number.MAX_SAFE_INTEGER) * 2n;
const numVal = Number(bigVal);  // 可能丢失精度

8.3 内存共享

JS 与 Wasm 共享线性内存

// Wasm 导出了 memory
const memory = instance.exports.memory;

// 创建视图读写 Wasm 内存
const i32View = new Int32Array(memory.buffer);
const f64View = new Float64Array(memory.buffer);
const u8View = new Uint8Array(memory.buffer);

// 写入数据到 Wasm 内存
const offset = 1024;
i32View[offset / 4] = 42;
f64View[offset / 8] = 3.14;

// 传递指针给 Wasm 函数
const result = instance.exports.process_data(offset);

内存增长处理

// ⚠️ 关键:memory.grow() 后 buffer 会变
let memory = instance.exports.memory;
let view = new Uint8Array(memory.buffer);

// 调用可能增长内存的函数
instance.exports.alloc(65536);

// buffer 可能已经变了!必须重新创建视图
view = new Uint8Array(memory.buffer);

// 安全的读取函数
function safeRead(memory, byteOffset, length) {
  // 每次都创建新的视图
  return new Uint8Array(memory.buffer, byteOffset, length);
}

传递字符串到 Wasm

// 方式 1:使用工具函数
function copyStringToWasm(str, instance) {
  const encoder = new TextEncoder();
  const encoded = encoder.encode(str + '\0');
  const ptr = instance.exports.malloc(encoded.length);
  const memView = new Uint8Array(instance.exports.memory.buffer);
  memView.set(encoded, ptr);
  return ptr;
}

function readStringFromWasm(ptr, instance) {
  const memView = new Uint8Array(instance.exports.memory.buffer);
  let end = ptr;
  while (memView[end] !== 0) end++;
  const decoder = new TextDecoder();
  return decoder.decode(memView.slice(ptr, end));
}

// 使用
const ptr = copyStringToWasm("Hello", instance);
instance.exports.process(ptr);
const result = readStringFromWasm(ptr, instance);
instance.exports.free(ptr);

结构化数据传递

// 将 JS 对象序列化到 Wasm 内存
function writeStruct(instance, offset, struct) {
  const view = new DataView(instance.exports.memory.buffer);
  view.setFloat64(offset, struct.x, true);       // 偏移 0: x (f64)
  view.setFloat64(offset + 8, struct.y, true);    // 偏移 8: y (f64)
  view.setInt32(offset + 16, struct.id, true);    // 偏移 16: id (i32)
  view.setUint8(offset + 20, struct.active ? 1 : 0); // 偏移 20: active (bool)
}

// 使用
const ptr = instance.exports.malloc(24);
writeStruct(instance, ptr, { x: 1.0, y: 2.0, id: 42, active: true });
instance.exports.process_entity(ptr);

8.4 Web Worker 中使用 Wasm

基础 Worker + Wasm

// main.js
const worker = new Worker('worker.js');

worker.postMessage({ type: 'init' });

worker.onmessage = (e) => {
  if (e.data.type === 'ready') {
    worker.postMessage({
      type: 'compute',
      data: new Float64Array([1, 2, 3, 4, 5])
    });
  }
  if (e.data.type === 'result') {
    console.log('Result:', e.data.value);
  }
};
// worker.js
let wasmInstance = null;

self.onmessage = async (e) => {
  const { type, data } = e.data;
  
  switch (type) {
    case 'init': {
      const imports = { /* ... */ };
      const { instance } = await WebAssembly.instantiateStreaming(
        fetch('compute.wasm'), imports
      );
      wasmInstance = instance;
      self.postMessage({ type: 'ready' });
      break;
    }
    case 'compute': {
      // 将数据复制到 Wasm 内存
      const memory = wasmInstance.exports.memory;
      const ptr = wasmInstance.exports.malloc(data.byteLength);
      new Float64Array(memory.buffer).set(data, ptr / 8);
      
      const result = wasmInstance.exports.process(ptr, data.length);
      self.postMessage({ type: 'result', value: result });
      wasmInstance.exports.free(ptr);
      break;
    }
  }
};

多 Worker 并行

// 创建 Worker 池
class WasmWorkerPool {
  constructor(wasmUrl, workerCount = navigator.hardwareConcurrency) {
    this.workers = [];
    this.taskQueue = [];
    this.nextWorker = 0;
    
    for (let i = 0; i < workerCount; i++) {
      const worker = new Worker('worker.js');
      worker.postMessage({ type: 'init', wasmUrl });
      worker.onmessage = (e) => this.handleResult(e);
      this.workers.push(worker);
    }
  }
  
  async compute(data) {
    return new Promise((resolve) => {
      this.taskQueue.push({ data, resolve });
      const worker = this.workers[this.nextWorker % this.workers.length];
      this.nextWorker++;
      worker.postMessage({ type: 'compute', data });
    });
  }
  
  handleResult(e) {
    const task = this.taskQueue.shift();
    task.resolve(e.data.value);
  }
}

// 使用
const pool = new WasmWorkerPool('heavy_compute.wasm', 4);
const results = await Promise.all([
  pool.compute(chunk1),
  pool.compute(chunk2),
  pool.compute(chunk3),
  pool.compute(chunk4),
]);

8.5 SharedArrayBuffer 共享内存

// main.js — 创建共享内存
const memory = new WebAssembly.Memory({
  initial: 1,
  maximum: 16,
  shared: true  // ⚠️ 需要 COOP/COEP 头
});

// 在多个 Worker 间共享同一块内存
const worker1 = new Worker('worker.js');
const worker2 = new Worker('worker.js');

worker1.postMessage({ memory, workerId: 0 });
worker2.postMessage({ memory, workerId: 1 });

// ⚠️ 需要服务器设置以下 HTTP 头:
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corp
// worker.js
self.onmessage = async (e) => {
  const { memory, workerId } = e.data;
  
  const imports = {
    env: {
      memory: memory,
      worker_id: workerId
    }
  };
  
  const { instance } = await WebAssembly.instantiateStreaming(
    fetch('parallel.wasm'), imports
  );
  
  // 多个 Worker 并行处理同一块内存的不同区域
  const offset = workerId * 1024 * 64;  // 每个 Worker 64KB 区域
  instance.exports.process(offset);
};

8.6 模块化集成(ESM + Bundler)

Vite 项目

// vite.config.js
export default {
  optimizeDeps: {
    exclude: ['my-wasm-pkg']
  },
  plugins: [{
    name: 'wasm',
    async configResolved(config) {
      // 确保 .wasm 文件正确处理
    }
  }]
};
// 使用 wasm-pack 输出的 ESM 模块
import init, { process_image } from 'my-wasm-pkg';

let initialized = false;

export async function ensureInit() {
  if (!initialized) {
    await init();
    initialized = true;
  }
}

export async function processImage(imageData) {
  await ensureInit();
  return process_image(imageData);
}

Webpack 配置

// webpack.config.js
module.exports = {
  experiments: {
    asyncWebAssembly: true
  },
  module: {
    rules: [
      {
        test: /\.wasm$/,
        type: 'webassembly/async'
      }
    ]
  }
};

8.7 错误处理

async function safeInstantiate(wasmUrl, imports = {}) {
  try {
    // 流式编译
    const response = await fetch(wasmUrl);
    
    if (!response.ok) {
      throw new Error(`Failed to fetch Wasm: ${response.status}`);
    }
    
    const contentType = response.headers.get('content-type');
    if (!contentType?.includes('application/wasm')) {
      console.warn('Incorrect MIME type, falling back to buffer');
      const buffer = await response.arrayBuffer();
      return await WebAssembly.instantiate(buffer, imports);
    }
    
    return await WebAssembly.instantiateStreaming(response, imports);
  } catch (error) {
    if (error instanceof WebAssembly.CompileError) {
      console.error('Wasm 编译失败:', error.message);
    } else if (error instanceof WebAssembly.LinkError) {
      console.error('Wasm 链接失败(导入不匹配):', error.message);
    } else if (error instanceof WebAssembly.RuntimeError) {
      console.error('Wasm 运行时错误:', error.message);
    } else {
      console.error('未知错误:', error);
    }
    throw error;
  }
}

常见错误类型

错误类型原因解决方案
CompileErrorWasm 二进制无效检查文件是否损坏
LinkError导入函数不匹配检查 imports 对象
RuntimeError内存越界、除零、trap调试 Wasm 代码
RangeError内存分配失败增大 maximum
CORS 错误跨域加载配置 CORS 头

8.8 Feature Detection

// 检测 Wasm 支持
function hasWasmSupport() {
  try {
    if (typeof WebAssembly === 'object' &&
        typeof WebAssembly.instantiate === 'function') {
      const module = new WebAssembly.Module(
        new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00])
      );
      return module instanceof WebAssembly.Module;
    }
  } catch (e) {}
  return false;
}

// 检测特定特性
function hasWasmSIMD() {
  try {
    const bytes = new Uint8Array([
      0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,
      10,10,1,8,0,65,0,253,15,253,98,11
    ]);
    return WebAssembly.validate(bytes);
  } catch (e) {
    return false;
  }
}

function hasWasmThreads() {
  try {
    const bytes = new Uint8Array([
      0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,
      5,4,1,3,1,1,10,11,1,9,0,254,3,0,2,7,0,11
    ]);
    return WebAssembly.validate(bytes) && typeof SharedArrayBuffer !== 'undefined';
  } catch (e) {
    return false;
  }
}

// 渐进增强策略
async function loadBestModule() {
  if (hasWasmSIMD()) {
    return loadModule('module-simd.wasm');
  } else if (hasWasmSupport()) {
    return loadModule('module.wasm');
  } else {
    return loadFallbackJS();
  }
}

8.9 实用工具函数

// wasm-utils.js

// 通用 Wasm 加载器
export async function loadWasm(wasmUrl, imports = {}) {
  if (typeof WebAssembly.instantiateStreaming === 'function') {
    const response = await fetch(wasmUrl);
    const { instance } = await WebAssembly.instantiateStreaming(response, imports);
    return instance.exports;
  }
  
  const response = await fetch(wasmUrl);
  const buffer = await response.arrayBuffer();
  const { instance } = await WebAssembly.instantiate(buffer, imports);
  return instance.exports;
}

// Wasm 内存管理包装器
export class WasmMemory {
  constructor(memory, exports) {
    this.memory = memory;
    this.exports = exports;
  }
  
  get buffer() { return this.memory.buffer; }
  
  writeU8(offset, data) {
    new Uint8Array(this.buffer).set(data, offset);
  }
  
  readU8(offset, length) {
    return new Uint8Array(this.buffer, offset, length).slice();
  }
  
  writeString(str, offset) {
    const encoded = new TextEncoder().encode(str + '\0');
    this.writeU8(offset, encoded);
    return offset;
  }
  
  readString(offset) {
    const view = new Uint8Array(this.buffer);
    let end = offset;
    while (view[end] !== 0) end++;
    return new TextDecoder().decode(view.slice(offset, end));
  }
  
  writeF64Array(arr, offset) {
    new Float64Array(this.buffer).set(arr, offset / 8);
  }
  
  readF64Array(offset, length) {
    return new Float64Array(this.buffer, offset / 8, length).slice();
  }
}

8.10 注意事项

⚠️ MIME 类型:服务器必须为 .wasm 文件返回 application/wasm,否则流式编译会失败。

⚠️ SharedArrayBuffer 限制:Chrome 要求 Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp 头才能使用 SharedArrayBuffer

⚠️ 内存视图失效:每次 Wasm 内存增长后,所有现有的 TypedArray 视图都变成无效的。务必重新创建。

⚠️ i64 兼容性:旧版 Safari 和某些环境不支持 BigInt,需要将 i64 拆分为两个 i32 传递。


8.11 扩展阅读


下一章09 - WASI 系统接口 — 让 Wasm 模块安全地访问操作系统功能。