跳到主要内容

WebAssembly 基础

问题

什么是 WebAssembly?它与 JavaScript 的关系是什么?在前端有哪些应用场景?

答案

1. 什么是 WebAssembly

WebAssembly(简称 Wasm)是一种低级的二进制格式,设计为高性能的编译目标。它不是用来替代 JavaScript 的,而是与 JS 互补

特点说明
二进制格式紧凑、解析快(比 JS 文本解析快 10-20 倍)
接近原生性能编译后的代码运行速度接近 C/C++
类型安全强类型(i32、i64、f32、f64)、内存安全
沙箱运行在安全沙箱中执行,无法直接访问 DOM 或系统
可移植与平台无关的字节码,所有主流浏览器支持
语言无关C/C++、Rust、Go、AssemblyScript 等都可编译为 Wasm

2. Wasm 与 JavaScript 的关系

Wasm ≠ JS 替代品

WebAssembly 不是用来替代 JavaScript 的。它更像是 JavaScript 的"协处理器",专门用于 计算密集型 任务。

维度JavaScriptWebAssembly
语法高级脚本语言低级二进制格式
类型动态类型静态强类型
解析速度需要解析文本二进制直接解码,极快
执行速度JIT 优化后较快,但有瓶颈接近原生
DOM 操作直接操作不能直接操作,需通过 JS
GC有垃圾回收手动管理内存(Wasm GC 提案中)
适用场景UI 逻辑、DOM 操作、业务代码计算密集、图形处理、编解码
开发体验生态丰富、调试方便需编译工具链

3. Wasm 模块结构

Wasm 有两种表示格式:

  • WAT(WebAssembly Text Format):可读的文本格式
  • WASM(WebAssembly Binary Format):二进制格式
;; WAT 格式 - 一个简单的加法函数
(module
;; 导出函数 "add"
(func $add (export "add") (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add
)

;; 导出内存
(memory (export "memory") 1) ;; 1 页 = 64KB
)

数据类型

Wasm 只有 4 种基本类型:

类型说明对应
i3232 位整数int / boolean
i6464 位整数long
f3232 位浮点数float
f6464 位浮点数double
没有字符串类型

Wasm 没有原生字符串类型。字符串需要通过线性内存(Linear Memory)以字节数组的形式在 JS 和 Wasm 之间传递。

4. JavaScript 与 Wasm 互操作

加载和实例化 Wasm 模块

// 方式 1: WebAssembly.instantiateStreaming(推荐,流式编译)
async function loadWasm(): Promise<WebAssembly.Instance> {
const response = await fetch('/math.wasm');
const { instance } = await WebAssembly.instantiateStreaming(response, {
// 导入对象:JS 函数供 Wasm 调用
env: {
log: (value: number) => console.log('Wasm says:', value),
memory: new WebAssembly.Memory({ initial: 1 }), // 1 页 = 64KB
},
});
return instance;
}

// 方式 2: 先获取 ArrayBuffer 再编译
async function loadWasm2(): Promise<WebAssembly.Instance> {
const response = await fetch('/math.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer);
const instance = await WebAssembly.instantiate(module, { env: {} });
return instance;
}

// 使用导出的函数
const instance = await loadWasm();
const exports = instance.exports as { add: (a: number, b: number) => number };
console.log(exports.add(1, 2)); // 3

内存交互

Wasm 使用线性内存(Linear Memory),JS 和 Wasm 共享同一块 ArrayBuffer

// Wasm 导出的内存
const memory = instance.exports.memory as WebAssembly.Memory;

// 通过 TypedArray 读写内存
const view = new Uint8Array(memory.buffer);

// 传递字符串到 Wasm
function passStringToWasm(str: string, offset: number): number {
const encoder = new TextEncoder();
const bytes = encoder.encode(str);
const wasmMemory = new Uint8Array(memory.buffer);
wasmMemory.set(bytes, offset);
return bytes.length;
}

// 从 Wasm 读取字符串
function readStringFromWasm(offset: number, length: number): string {
const decoder = new TextDecoder();
const bytes = new Uint8Array(memory.buffer, offset, length);
return decoder.decode(bytes);
}

// 传递数组
function passArrayToWasm(arr: Float64Array, offset: number): void {
const wasmView = new Float64Array(memory.buffer, offset, arr.length);
wasmView.set(arr);
}

5. 编译工具链

Emscripten(C/C++ → Wasm)

Emscripten 是最成熟的 C/C++ → Wasm 编译工具链:

// math.c
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
# 编译
emcc math.c -o math.js -s EXPORTED_FUNCTIONS='["_fibonacci"]' -s MODULARIZE=1
// 使用 Emscripten 编译产物
import createModule from './math.js';

const Module = await createModule();
console.log(Module._fibonacci(40)); // 比纯 JS 快数倍

wasm-pack(Rust → Wasm)

wasm-pack 是 Rust → Wasm 的工具链,配合 wasm-bindgen 自动生成 JS 绑定:

// src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
match n {
0 | 1 => n,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
wasm-pack build --target web
import init, { fibonacci, greet } from './pkg/my_wasm.js';

await init(); // 初始化 Wasm 模块
console.log(fibonacci(40)); // 计算密集型任务
console.log(greet('World')); // 字符串自动转换

AssemblyScript(TypeScript-like → Wasm)

AssemblyScript 使用类 TypeScript 语法,前端开发者最友好:

// assembly/index.ts(AssemblyScript,非标准 TS)
export function fibonacci(n: i32): i32 {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}

export function sum(arr: Int32Array): i32 {
let total: i32 = 0;
for (let i = 0; i < arr.length; i++) {
total += unchecked(arr[i]); // unchecked 跳过边界检查,更快
}
return total;
}
npx asc assembly/index.ts --outFile build/optimized.wasm --optimize

6. WASI — WebAssembly System Interface

WASI 让 Wasm 在浏览器之外运行(Node.js、Deno、独立运行时),提供标准化的系统接口:

WASI 的应用场景:

  • Serverless / 边缘计算:冷启动快、安全隔离(Cloudflare Workers、Vercel Edge)
  • 插件系统:安全的第三方代码执行
  • 跨平台 CLI:一次编译,到处运行

7. Wasm 的局限性

局限说明解决方案
无法直接操作 DOMWasm 运行在沙箱中通过 JS 桥接
无 GC手动管理内存Wasm GC 提案、使用 Rust 的所有权系统
字符串传递开销需要通过内存拷贝wasm-bindgen 自动处理
调试困难二进制格式不可读Source Map、DWARF 调试信息
包体积.wasm 文件可能较大wasm-opt 优化、Brotli 压缩
加载时间首次编译有开销流式编译 + 缓存

常见面试问题

Q1: WebAssembly 比 JavaScript 快在哪里?

答案

阶段JavaScriptWebAssembly
解析文本→AST→字节码,耗时二进制直接解码,快 10-20x
编译JIT 分层编译,有预热期AOT/流式编译,启动即快
执行动态类型,需类型检查静态类型,直接执行
优化JIT 可能去优化(deopt)编译时已优化,无 deopt
GCGC 暂停(虽短但存在)无 GC 暂停
内存布局对象分散在堆中线性内存,缓存友好

但 Wasm 不是在所有场景都快:

  • DOM 操作:JS 更快(Wasm 需要跨边界调用)
  • 短小函数:JS JIT 优化后差距不大
  • I/O 密集:瓶颈在 I/O,不在计算

Q2: WebAssembly 能替代 JavaScript 吗?

答案

不能,也不应该。设计上它们是互补关系:

  • JS 的强项:DOM 操作、事件处理、UI 逻辑、快速原型、丰富生态
  • Wasm 的强项:数值计算、图形处理、编解码、密码学、物理模拟

最佳模式是 JS 为主、Wasm 补充:用 JS 处理 UI 和业务逻辑,将计算密集的热点用 Wasm 实现。

Q3: 如何在项目中引入 WebAssembly?

答案

引入路径从低到高:

  1. 使用现有 Wasm 库(最简单)

    // 例如 sql.js(SQLite 的 Wasm 版)
    import initSqlJs from 'sql.js';
    const SQL = await initSqlJs();
    const db = new SQL.Database();
  2. AssemblyScript(前端友好)

    • 类 TypeScript 语法,学习曲线低
    • 适合简单的计算函数
  3. Rust + wasm-pack(推荐,性能+安全)

    • 零成本抽象、内存安全
    • wasm-bindgen 自动生成 JS 绑定
  4. C/C++ + Emscripten(移植老项目)

    • 适合将已有 C/C++ 库移植到 Web

Q4: Wasm 的线性内存模型是什么?

答案

Wasm 使用一块连续的 ArrayBuffer 作为内存,称为线性内存(Linear Memory):

// 创建 1 页(64KB)内存
const memory = new WebAssembly.Memory({ initial: 1, maximum: 10 });

// JS 侧通过 TypedArray 视图读写
const buffer = new Int32Array(memory.buffer);
buffer[0] = 42;

// Wasm 侧通过指针访问同一块内存
// (i32.load (i32.const 0)) → 42
  • 单位是 页(Page),1 页 = 64KB
  • 可通过 memory.grow(n) 动态增长
  • JS 和 Wasm 共享同一块 buffer,是主要的数据交换方式
  • 内存增长后,原有的 TypedArray 视图会失效,需要重新创建

Q5: WebAssembly 在前端有哪些典型应用?

答案

应用代表项目说明
图片/视频处理FFmpeg.wasm、Squoosh浏览器端编解码
设计工具Figma、AutoCAD Web复杂图形渲染
代码编辑器VS Code Web(部分)语法高亮、语言服务
游戏引擎Unity WebGL、Godot3D 渲染、物理模拟
数据库sql.js、DuckDB-Wasm浏览器端 SQL
加密libsodium.js高性能密码学
PDFPDF.js(部分)PDF 渲染
AI 推理ONNX Runtime Web浏览器端模型推理
地图Google Earth3D 地球渲染
OfficeGoogle Sheets计算引擎

相关链接