WebAssembly 入门
什么是 WebAssembly
WebAssembly(简称 Wasm)是一种低级的二进制指令格式,可以在现代浏览器中以接近原生的速度运行。它不是 JavaScript 的替代品,而是与之互补——当需要高性能计算时,Wasm 是理想选择。
核心特点
- 高性能:接近原生速度,比 JS 快数倍到数十倍
- 类型安全:具有类型系统的二进制格式
- 可移植:一次编译,任何支持 Wasm 的环境都能运行
- 安全:在沙箱中运行,遵循浏览器的同源策略
- 多语言:可用 C/C++、Rust、Go、Python 等语言编写
为什么需要 WebAssembly
JavaScript 虽然灵活,但在计算密集场景下存在性能瓶颈:
- 图像/视频处理
- 3D 渲染(游戏、CAD)
- 加密解密
- 数据压缩
- 科学计算
- AI/机器学习
快速上手(使用 Rust)
安装工具链
# 安装 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 添加 wasm 目标
rustup target add wasm32-unknown-unknown
# 安装 wasm-pack
cargo install wasm-pack
创建项目
cargo new --lib wasm-game-of-life
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("你好, {}! 来自 WebAssembly 的问候", name)
}
编译
wasm-pack build --target web
在 JavaScript 中使用
import init, { fibonacci, greet } from './pkg/wasm_game_of_life.js';
async function main() {
await init(); // 初始化 Wasm 模块
console.log(greet('World')); // 你好, World! 来自 WebAssembly 的问候
console.log(fibonacci(42)); // 267914296
}
main();
在 Go 中使用 WebAssembly
// main.go
package main
import "syscall/js"
func add(this js.Value, args []js.Value) interface{} {
a := args[0].Int()
b := args[1].Int()
return a + b
}
func main() {
c := make(chan struct{}, 0)
js.Global().Set("addWasm", js.FuncOf(add))
<-c // 阻止程序退出
}
GOOS=js GOARCH=wasm go build -o main.wasm main.go
// 加载 Go Wasm 模块
const go = new Go();
const result = await WebAssembly.instantiateStreaming(
fetch('main.wasm'), go.importObject
);
go.run(result.instance);
console.log(addWasm(3, 4)); // 7
WebAssembly 与 JavaScript 交互
JS 调用 Wasm
// 加载 Wasm 模块
const response = await fetch('module.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {
env: {
print: (n) => console.log(n),
},
});
// 调用导出函数
instance.exports.myFunction(42);
// 访问内存
const memory = instance.exports.memory;
const view = new Int32Array(memory.buffer);
Wasm 调用 JS
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
#[wasm_bindgen(js_namespace = window)]
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn show_message(name: &str) {
log(&format!("Hello, {}!", name));
alert("来自 Wasm 的消息");
}
性能对比
// JavaScript 实现
function fibonacciJS(n) {
if (n <= 1) return n;
return fibonacciJS(n - 1) + fibonacciJS(n - 2);
}
// Rust (Wasm) 实现将在浏览器中运行
// fibonacci(42): JS ≈ 1800ms, Wasm ≈ 120ms
// Wasm 快了约 15 倍
使用场景
1. 图像处理
#[wasm_bindgen]
pub fn grayscale(pixels: &[u8], width: u32, height: u32) -> Vec<u8> {
let mut result = Vec::with_capacity(pixels.len());
for chunk in pixels.chunks(4) {
let gray = (0.299 * chunk[0] as f32 + 0.587 * chunk[1] as f32 + 0.114 * chunk[2] as f32) as u8;
result.push(gray);
result.push(gray);
result.push(gray);
result.push(chunk[3]); // 保持 alpha
}
result
}
2. 数据压缩
// 使用 brotli 压缩库
#[wasm_bindgen]
pub fn compress_data(input: &[u8]) -> Vec<u8> {
brotli::compress(input, 6, Default::default())
}
#[wasm_bindgen]
pub fn decompress_data(input: &[u8]) -> Vec<u8> {
brotli::decompress(input, Default::default())
}
局限性
- 无法直接操作 DOM:需要通过 JS 桥接
- 启动开销:加载和初始化需要时间
- 调试困难:比 JS 更难调试
- 单线程:仍需 Web Workers 实现并发
- 生态相对年轻:工具链和库不如 JS 丰富
未来发展方向
- WASI(WebAssembly System Interface):将 Wasm 扩展到服务器端
- 多线程支持:WebAssembly Threads 提案
- GC 支持:让 Java、Kotlin、Dart 等语言能编译到 Wasm
- 组件模型:模块化、可组合的 Wasm 组件
总结
WebAssembly 适合做 JS 不擅长的计算密集型任务。它不是要取代 JS,而是扩展 Web 平台的能力边界。对于前端开发者,了解 Wasm 的基础概念和使用方式,能让你在遇到性能瓶颈时多一个有力的工具。