引言

WebAssembly (Wasm) 正悄然改变前端开发的边界。它让浏览器可以运行接近原生速度的 C/C++、Rust、Python 代码,打开了从前难以想象的应用场景——从图像处理到 3D 渲染再到机器学习推理。

WebAssembly

WebAssembly 是什么

WebAssembly 是一种低级的、类汇编的二进制格式,可以在浏览器中以接近原生的速度运行。它不是用来替代 JavaScript 的,而是与 JS 协同工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
C/C++/Rust/Go 源码


编译器 (Emscripten/wasm-pack/...)


.wasm 二进制文件


JavaScript 加载和调用


浏览器 WASM 虚拟机

性能对比

任务 JavaScript WebAssembly 加速比
Fibonacci(45) 8500ms 420ms ~20x
图像滤镜 120ms 15ms ~8x
JSON 解析 45ms 8ms ~5.6x
AES 加密 380ms 35ms ~10x

用 C 语言创建 Wasm 模块

环境搭建

1
2
3
4
5
6
# 安装 Emscripten SDK
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

编写 C 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// math.c
#include <math.h>
#include <emscripten.h>

// EMSCRIPTEN_KEEPALIVE 防止函数被优化掉
EMSCRIPTEN_KEEPALIVE
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}

EMSCRIPTEN_KEEPALIVE
double compute_mandelbrot(double cx, double cy, int max_iter) {
double x = 0, y = 0;
int iter;
for (iter = 0; iter < max_iter; iter++) {
double x2 = x * x, y2 = y * y;
if (x2 + y2 > 4.0) break;
y = 2 * x * y + cy;
x = x2 - y2 + cx;
}
return (double)iter / max_iter;
}

编译

1
2
3
4
5
6
# 编译为 Wasm
emcc math.c -o math.js \
-s WASM=1 \
-s EXPORTED_FUNCTIONS='["_fibonacci", "_compute_mandelbrot"]' \
-s EXPORTED_RUNTIME_METHODS='["cwrap"]' \
-O3

JavaScript 调用

1
2
3
4
5
6
7
8
9
10
11
// 加载 Wasm 模块
async function loadWasm() {
const Module = await import('./math.js');

const fibonacci = Module.cwrap('fibonacci', 'number', ['number']);
const mandelbrot = Module.cwrap('compute_mandelbrot', 'number',
['number', 'number', 'number']);

console.log('Fibonacci(40):', fibonacci(40));
console.log('Mandelbrot:', mandelbrot(-0.7, 0.27, 1000));
}

用 Rust 创建 Wasm 模块

环境搭建

1
2
3
4
5
6
# 安装 wasm-pack
cargo install wasm-pack

# 创建项目
cargo new --lib wasm-image-processor
cd wasm-image-processor

编写 Rust 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/lib.rs
use wasm_bindgen::prelude::*;
use web_sys::ImageData;

#[wasm_bindgen]
pub fn grayscale(image_data: &mut [u8]) {
for chunk in image_data.chunks_mut(4) {
// RGBA -> Gray
let gray = (chunk[0] as f32 * 0.299
+ chunk[1] as f32 * 0.587
+ chunk[2] as f32 * 0.114) as u8;
chunk[0] = gray; // R
chunk[1] = gray; // G
chunk[2] = gray; // B
// chunk[3] Alpha 保持不变
}
}

编译和使用

1
wasm-pack build --target web
1
2
3
4
5
6
7
8
9
10
11
12
import init, { grayscale } from './pkg/wasm_image_processor.js';

async function applyGrayscale() {
await init();

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

grayscale(imageData.data);
ctx.putImageData(imageData, 0, 0);
}

在浏览器中运行 Python

Pyodide

Pyodide 将 CPython 解释器编译成了 WebAssembly,让你可以在浏览器中运行 Python 和科学计算库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script src="https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.js"></script>
<script>
async function main() {
let pyodide = await loadPyodide();

// 安装 NumPy
await pyodide.loadPackage('numpy');

// 运行 Python 代码
let result = pyodide.runPython(`
import numpy as np

# 矩阵运算
a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.array([[7, 8], [9, 10], [11, 12]])
c = np.dot(a, b)

# 返回结果
c.tolist()
`);

console.log('矩阵乘法结果:', result.toJs());
// [[58, 64], [139, 154]]
}

main();
</script>

SIMD 和线程支持

SIMD 加速

1
2
3
4
5
6
7
8
9
10
11
12
#include <wasm_simd128.h>
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
void add_arrays_simd(float* a, float* b, float* result, int len) {
for (int i = 0; i < len; i += 4) {
v128_t va = wasm_v128_load(&a[i]);
v128_t vb = wasm_v128_load(&b[i]);
v128_t vresult = wasm_f32x4_add(va, vb);
wasm_v128_store(&result[i], vresult);
}
}

编译时启用 SIMD:

1
emcc simd.c -o simd.js -msimd128 -O3

共享内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 在 JS 和 Wasm 之间共享内存,避免复制
const memory = new WebAssembly.Memory({
initial: 256, // 256 页 = 16MB
maximum: 512,
shared: true, // 启用共享内存
});

const importObject = {
env: { memory }
};

const wasm = await WebAssembly.instantiate(wasmBytes, importObject);
const wasmMemory = new Float64Array(memory.buffer);

// 写入数据(无需复制)
for (let i = 0; i < 100000; i++) {
wasmMemory[i] = Math.random();
}

// Wasm 函数直接读取同一块内存
wasm.instance.exports.process_array(0, 100000);

实际应用场景

场景 技术方案 效果
图片压缩 C++ 编译 Wasm + SIMD 比 JS 快 20x
Figma 的矢量渲染 C++ → Wasm 复杂图形流畅 60fps
AutoCAD Web 30 年 C++ 代码库 → Wasm 完整 CAD 引擎在浏览器中运行
TensorFlow.js Wasm 后端 Wasm + SIMD + 多线程 使浏览器端 ML 推理可实用
Google Earth Web C++ → Wasm 3D 地图流畅渲染

调试工具

1
2
3
4
# 将 .wasm 转换为可读的 WAT 格式
wasm2wat module.wasm -o module.wat

# 浏览器中在 Console 查看 Wasm 栈跟踪

Chrome DevTools 现在支持加载 .wasm 源码映射,可以直接在浏览器中调试 C/C++ 源码。

WASI:走出浏览器的 Wasm

WebAssembly System Interface (WASI) 让 Wasm 在浏览器之外也能运行:

1
2
3
4
5
# 使用 Wasmtime 运行 Wasm
wasmtime hello.wasm

# 使用 WasmEdge 运行(轻量级,适合边缘计算)
wasmedge hello.wasm
1
2
3
4
5
6
7
8
9
// Rust 代码编译为 WASI 目标
#[tokio::main]
async fn main() -> Result<()> {
// 标准的 Rust 代码,可以文件 I/O、网络等
let response = reqwest::get("https://api.example.com/data").await?;
let data: serde_json::Value = response.json().await?;
println!("{}", data);
Ok(())
}

编译:

1
2
cargo build --target wasm32-wasi
wasmtime target/wasm32-wasi/debug/app.wasm

总结

WebAssembly 正在打破「前端只能用 JavaScript」的天花板。它让现有的 C/C++/Rust 代码库可以直接运行在浏览器中,也让前端能够胜任更复杂的计算任务。

Wasm 不会取代 JavaScript,但会成为前端工具箱中最锋利的刀——处理计算密集型任务时的不二之选。