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

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 模块。