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

WebAssembly 入门教程 / 07 - AssemblyScript

07 - AssemblyScript

如果你熟悉 TypeScript,AssemblyScript 让你几乎零学习成本地编写 WebAssembly。


7.1 AssemblyScript 简介

AssemblyScript 是 TypeScript 的一个严格子集变体,设计用于直接编译为 WebAssembly。它使用 TypeScript 语法,但语义更接近底层系统语言。

与 TypeScript 的核心差异

特性TypeScriptAssemblyScript
类型推断动态 + 静态纯静态
数字类型number (f64)i32, i64, f32, f64 显式选择
null 处理null | T`T
垃圾回收运行时 GC引用计数或手动管理
标准库Math, Date, Map替代实现或内置
any 类型支持❌ 不支持
联合类型支持❌ 受限支持
接口支持❌ 不支持
枚举支持✅ 支持(映射为整数)
泛型支持✅ 受限支持
编译目标JavaScriptWebAssembly

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 对应
i88 bit-128 ~ 127i32(截断)
u88 bit0 ~ 255i32(截断)
i1616 bit-32768 ~ 32767i32(截断)
u1616 bit0 ~ 65535i32(截断)
i3232 bit-2^31 ~ 2^31-1i32
u3232 bit0 ~ 2^32-1i32
i6464 bit-2^63 ~ 2^63-1i64
u6464 bit0 ~ 2^64-1i64
f3232 bitIEEE 754 单精度f32
f6464 bitIEEE 754 双精度f64
bool1 bittrue/falsei32

类型转换

// 显式类型转换(强制转换)
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 差异详解

特性TypeScriptAssemblyScript
numberf64不存在,必须指定 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 模块。