WebAssembly 入门教程 / 07 - AssemblyScript
07 - AssemblyScript
如果你熟悉 TypeScript,AssemblyScript 让你几乎零学习成本地编写 WebAssembly。
7.1 AssemblyScript 简介
AssemblyScript 是 TypeScript 的一个严格子集变体,设计用于直接编译为 WebAssembly。它使用 TypeScript 语法,但语义更接近底层系统语言。
与 TypeScript 的核心差异
| 特性 | TypeScript | AssemblyScript |
|---|---|---|
| 类型推断 | 动态 + 静态 | 纯静态 |
| 数字类型 | number (f64) | i32, i64, f32, f64 显式选择 |
| null 处理 | null | T | `T |
| 垃圾回收 | 运行时 GC | 引用计数或手动管理 |
| 标准库 | Math, Date, Map 等 | 替代实现或内置 |
| any 类型 | 支持 | ❌ 不支持 |
| 联合类型 | 支持 | ❌ 受限支持 |
| 接口 | 支持 | ❌ 不支持 |
| 枚举 | 支持 | ✅ 支持(映射为整数) |
| 泛型 | 支持 | ✅ 受限支持 |
| 编译目标 | JavaScript | WebAssembly |
7.2 快速开始
项目初始化
mkdir as-demo && cd as-demo
npm init -y
npm install --save-dev assemblyscript
npx asinit .
项目结构
as-demo/
├── asconfig.json # AssemblyScript 配置
├── assembly/
│ └── index.ts # 入口文件
├── build/
│ └── module.wasm # 编译输出
├── tests/
│ └── index.js # 测试文件
└── package.json
基础示例
// assembly/index.ts
// 导出函数
export function add(a: i32, b: i32): i32 {
return a + b;
}
export function fibonacci(n: i32): i32 {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 字符串处理
export function greet(name: string): string {
return "Hello, " + name + "!";
}
// 数组操作
export function sumArray(arr: Array<i32>): i32 {
let sum: i32 = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
编译与使用
# 编译
npx asc assembly/index.ts --outFile build/module.wasm --optimize
# 优化
npx asc assembly/index.ts --outFile build/module.wasm --optimizeLevel 3 --shrinkLevel 1
// load.js
const fs = require('fs');
const wasmBuffer = fs.readFileSync('./build/module.wasm');
WebAssembly.instantiate(wasmBuffer).then(({ instance }) => {
const { add, fibonacci, greet, sumArray } = instance.exports;
console.log(add(10, 20)); // 30
console.log(fibonacci(10)); // 55
// 字符串需要特殊处理(AssemblyScript 使用自己的字符串格式)
// 需要 loader 来处理字符串和数组
});
使用 as-loader
npm install as-loader
const loader = require('as-loader');
const wasmModule = await loader.instantiate('./build/module.wasm');
// 自动处理字符串和数组转换
console.log(wasmModule.greet("World"));
console.log(wasmModule.sumArray([1, 2, 3, 4, 5]));
7.3 类型系统
基本类型
// 整数类型
let a: i8 = 127; // 8 位有符号整数
let b: u8 = 255; // 8 位无符号整数
let c: i16 = 32767; // 16 位有符号整数
let d: u16 = 65535; // 16 位无符号整数
let e: i32 = 2147483647; // 32 位有符号整数(最常用)
let f: u32 = 4294967295; // 32 位无符号整数
let g: i64 = 9223372036854775807; // 64 位有符号整数
let h: u64: u64 = 18446744073709551615;
// 浮点类型
let i: f32 = 3.14; // 32 位浮点
let j: f64 = 3.141592653589793; // 64 位浮点(默认)
// 布尔类型
let k: bool = true; // 编译为 i32 (0/1)
// 字符串类型
let l: string = "hello"; // 引用类型
// 数值字面量
let m = 0xFF; // 十六进制
let n = 0b1010; // 二进制
let o = 0o77; // 八进制
let p = 1_000_000; // 下划线分隔
数值类型速查
| 类型 | 位宽 | 范围 | Wasm 对应 |
|---|---|---|---|
i8 | 8 bit | -128 ~ 127 | i32(截断) |
u8 | 8 bit | 0 ~ 255 | i32(截断) |
i16 | 16 bit | -32768 ~ 32767 | i32(截断) |
u16 | 16 bit | 0 ~ 65535 | i32(截断) |
i32 | 32 bit | -2^31 ~ 2^31-1 | i32 |
u32 | 32 bit | 0 ~ 2^32-1 | i32 |
i64 | 64 bit | -2^63 ~ 2^63-1 | i64 |
u64 | 64 bit | 0 ~ 2^64-1 | i64 |
f32 | 32 bit | IEEE 754 单精度 | f32 |
f64 | 64 bit | IEEE 754 双精度 | f64 |
bool | 1 bit | true/false | i32 |
类型转换
// 显式类型转换(强制转换)
let a: i32 = 42;
let b: f64 = <f64>a; // i32 → f64
let c: i32 = <i32>3.14; // f64 → i32 (截断为 3)
let d: u32 = <u32>-1; // i32 → u32 (4294967295)
// 数字转字符串
let s: string = a.toString();
// 字符串转数字(需要检查返回值)
let n: i32 = I32.parseInt("42");
let f: f64 = F64.parseFloat("3.14");
7.4 类与面向对象
// assembly/geometry.ts
export class Vector3 {
x: f64;
y: f64;
z: f64;
constructor(x: f64 = 0.0, y: f64 = 0.0, z: f64 = 0.0) {
this.x = x;
this.y = y;
this.z = z;
}
// 实例方法
length(): f64 {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
normalize(): Vector3 {
const len = this.length();
return new Vector3(this.x / len, this.y / len, this.z / len);
}
add(other: Vector3): Vector3 {
return new Vector3(
this.x + other.x,
this.y + other.y,
this.z + other.z
);
}
dot(other: Vector3): f64 {
return this.x * other.x + this.y * other.y + this.z * other.z;
}
cross(other: Vector3): Vector3 {
return new Vector3(
this.y * other.z - this.z * other.y,
this.z * other.x - this.x * other.z,
this.x * other.y - this.y * other.x
);
}
toString(): string {
return `Vector3(${this.x}, ${this.y}, ${this.z})`;
}
}
// 继承
export class Point3D extends Vector3 {
w: f64;
constructor(x: f64, y: f64, z: f64, w: f64 = 1.0) {
super(x, y, z);
this.w = w;
}
perspectiveDivide(): Vector3 {
return new Vector3(this.x / this.w, this.y / this.w, this.z / this.w);
}
}
泛型
// 泛型类
export class Pair<T1, T2> {
first: T1;
second: T2;
constructor(first: T1, second: T2) {
this.first = first;
this.second = second;
}
}
// 使用
let p = new Pair<i32, string>(42, "hello");
// 泛型函数
export function clamp<T>(value: T, min: T, max: T): T {
return value < min ? min : (value > max ? max : value);
}
let c = clamp<i32>(15, 0, 10); // 10
7.5 内存管理
手动内存管理
// AssemblyScript 提供了内存管理工具
// 分配原始内存
let ptr = __new(100, idof<ArrayBuffer>());
// 获取内存视图
let buffer = changetype<ArrayBuffer>(ptr);
// 使用 Unmanaged<T> 避免引用计数
import { Unmanaged } from "assemblyscript";
class Particle {
x: f64;
y: f64;
vx: f64;
vy: f64;
}
// 手动分配,不参与 GC
let p = changetype<Particle>(__new(offsetof<Particle>(), idof<Particle>()));
// 手动释放
__free(changetype<usize>(p));
静态内存(Arena 模式)
// 为高性能场景使用固定大小的内存池
const POOL_SIZE: i32 = 1024 * 1024; // 1MB
// 静态数组作为内存池
let memoryPool = new StaticArray<u8>(POOL_SIZE);
let allocOffset: i32 = 0;
export function arenaAlloc(size: i32): i32 {
const aligned = (size + 7) & ~7; // 8 字节对齐
const ptr = allocOffset;
allocOffset += aligned;
if (allocOffset > POOL_SIZE) {
throw new Error("Out of memory");
}
return changetype<usize>(memoryPool) + ptr;
}
export function arenaReset(): void {
allocOffset = 0;
}
7.6 导入与导出
导入宿主函数
// 声明外部导入
@external("env", "console_log")
declare function consoleLog(msg: string): void;
@external("env", "random")
declare function random(): f64;
@external("env", "performance_now")
declare function now(): f64;
export function benchmark(iterations: i32): f64 {
const start = now();
for (let i = 0; i < iterations; i++) {
// 计算密集操作
let x: f64 = 0;
for (let j = 0; j < 1000; j++) {
x += random();
}
}
const elapsed = now() - start;
consoleLog("Elapsed: " + elapsed.toString() + "ms");
return elapsed;
}
// JavaScript 侧提供导入
const imports = {
env: {
console_log: (msg) => console.log(msg),
random: () => Math.random(),
performance_now: () => performance.now()
}
};
const wasmModule = await loader.instantiate('./build/module.wasm', imports);
wasmModule.benchmark(1000);
导出内存
// 导出内存供 JS 直接访问
export function getMemoryBuffer(): ArrayBuffer {
return memory.buffer;
}
// 导出数据指针
let dataBuffer = new Float32Array(1024);
export function getDataPtr(): usize {
return changetype<usize>(dataBuffer.dataStart);
}
export function getDataLength(): i32 {
return dataBuffer.length;
}
7.7 内置函数与标准库
数学函数
// 直接使用 Math 命名空间
let a: f64 = Math.sqrt(16.0); // 4.0
let b: f64 = Math.sin(Math.PI / 2); // 1.0
let c: f64 = Math.log(Math.E); // 1.0
let d: f64 = Math.pow(2.0, 10.0); // 1024.0
let e: f64 = Math.abs(-42.0); // 42.0
let f: f64 = Math.min(a, b);
let g: f64 = Math.max(a, b);
let h: f64 = Math.floor(3.7); // 3.0
let i: f64 = Math.ceil(3.2); // 4.0
let j: f64 = Math.round(3.5); // 4.0
// 常量
const PI: f64 = Math.PI;
const E: f64 = Math.E;
const EPSILON: f64 = Mathf.EPSILON; // f32 精度
内置函数
// 类型大小
let size = offsetof<Vector3>(); // 结构体大小
// 内存操作
memory.copy(dst, src, size); // 内存复制
memory.fill(ptr, value, size); // 内存填充
// 不可达代码
unreachable(); // 触发 trap
// 数值边界
let maxI32 = i32.MAX_VALUE; // 2147483647
let minI32 = i32.MIN_VALUE; // -2147483648
let maxF64 = f64.MAX_VALUE; // 1.7976931348623157e+308
7.8 数组与字符串
数组
// 基本数组
let arr = new Array<i32>(100);
arr[0] = 42;
arr.push(100);
let len = arr.length;
// 静态数组(更高效,固定大小)
let staticArr = new StaticArray<i32>(100);
store<i32>(changetype<usize>(staticArr), 42, 0 * sizeof<i32>());
// TypedArray
let floatArr = new Float64Array(100);
floatArr[0] = 3.14;
// Uint8Array(字节数组)
let bytes = new Uint8Array(1024);
bytes[0] = 0xFF;
字符串处理
// 字符串拼接
let greeting: string = "Hello" + ", " + "World!";
// 字符串长度
let len: i32 = greeting.length;
// 子串
let sub: string = greeting.substring(0, 5); // "Hello"
// 字符串比较
let eq: bool = greeting == "Hello, World!";
// 字符串转大写/小写
let upper: string = greeting.toUpperCase();
let lower: string = greeting.toLowerCase();
// 字符串包含
let hasWorld: bool = greeting.includes("World");
// 字符串分割
let parts: Array<string> = greeting.split(", ");
// parseInt / parseFloat
let num: i32 = I32.parseInt("42");
let pi: f64 = F64.parseFloat("3.14");
7.9 与 TypeScript 差异详解
| 特性 | TypeScript | AssemblyScript |
|---|---|---|
number | f64 | 不存在,必须指定 i32/f64 等 |
let x = 42 | 推断为 number | 推断为 i32 |
null | 可赋给任何类型 | 仅可赋给 nullable 类型 |
undefined | 存在 | 不存在 |
any | 存在 | 不存在 |
void | 存在 | 存在 |
interface | 支持 | 不支持 |
type 别名 | 支持 | 不支持 |
enum | 支持 | 支持(整数映射) |
for...of | 支持 | 支持 |
async/await | 支持 | 不支持 |
Promise | 支持 | 不支持 |
Map/Set | 内置 | 有替代实现 |
| 解构赋值 | 支持 | 不支持 |
可选链 ?. | 支持 | 不支持 |
空值合并 ?? | 支持 | 不支持 |
| 模板字符串 | 支持 | 支持 |
| 剩余参数 | 支持 | 有限支持 |
| 装饰器 | 支持 | 支持(@external, @final 等) |
常见陷阱
// ❌ 错误:没有 number 类型
let x: number = 42;
// ✅ 正确:显式指定类型
let x: i32 = 42;
// ❌ 错误:不能将 null 赋给非 nullable 类型
let y: i32 = null;
// ✅ 正确:使用 nullable 类型
let y: i32 | null = null;
// ❌ 错误:不能使用 ===(没有引用类型比较语义)
if (a === b) {}
// ✅ 正确:使用 ==
if (a == b) {}
// ❌ 错误:不支持的三元运算符
let x = cond ? a : b; // 有限支持
// ✅ 正确:使用 if-else
let x: i32;
if (cond) { x = a; } else { x = b; }
7.10 性能优化建议
// 1. 使用 StaticArray 替代 Array(减少 GC 压力)
let buffer = new StaticArray<f32>(1024);
// 2. 使用 unchecked 绕过边界检查(谨慎使用)
unchecked(arr[i] = value);
// 3. 使用内联
@inline function fastAdd(a: i32, b: i32): i32 {
return a + b;
}
// 4. 使用 @final 避免虚函数调用开销
@final class FastVector {
// ...
}
// 5. 使用 @unmanaged 避免引用计数
@unmanaged class RawBuffer {
data: u8;
}
// 6. 数值计算优先使用整数
// ❌ 慢
let x: f64 = 0;
for (let i: f64 = 0; i < 1000; i++) { x += i; }
// ✅ 快
let x: f64 = 0;
for (let i: i32 = 0; i < 1000; i++) { x += <f64>i; }
7.11 测试
// assembly/__tests__/math.spec.ts
import { add, fibonacci } from "../index";
describe("add", () => {
it("should add two positive numbers", () => {
expect(add(2, 3)).toBe(5);
});
it("should handle negative numbers", () => {
expect(add(-1, 1)).toBe(0);
});
});
describe("fibonacci", () => {
it("should compute fibonacci(0) = 0", () => {
expect(fibonacci(0)).toBe(0);
});
it("should compute fibonacci(10) = 55", () => {
expect(fibonacci(10)).toBe(55);
});
});
# 运行测试
npm test
7.12 注意事项
⚠️ 不是 TypeScript:虽然语法相似,但 AssemblyScript 不是 TypeScript 的子集。直接将现有 TypeScript 代码编译为 AssemblyScript 几乎不可能,需要重写。
⚠️ 标准库有限:AssemblyScript 的标准库比 Node.js/Browser 的标准库小得多。复杂功能需要自己实现或使用社区库。
⚠️ GC 行为:AssemblyScript 使用引用计数 + 标记清除的混合 GC。在性能敏感场景中,尽量使用值类型和
@unmanaged来避免 GC 开销。
⚠️
==vs===:AssemblyScript 中==用于值比较,不存在===(因为没有隐式类型转换问题)。
7.13 扩展阅读
下一章:08 - JavaScript 集成 — 在浏览器和 Node.js 中高效使用 Wasm 模块。