AssemblyScript 入门指南
作者:Danny Guo
翻译:疯狂的技术宅
原文: https:// blog.logrocket.com/the- introductory-guide-to-assemblyscript/
未经允许严禁转载
WebAssembly (Wasm)是 Web 浏览器中相对较新的功能,但它地扩展了把 Web 作为服务应用平台的功能潜力。
对于 Web 开发人员来说,学习使用 WebAssembly 可能会有一个艰难的过程,但是 AssemblyScript 提供了一种解决方法。首先让我们看一下为什么 WebAssembly 是一项很有前途的技术,然后再看怎样 AssemblyScript 挖掘潜力。
WebAssembly
WebAssembly 是浏览器的低级语言,为开发人员提供了除 JavaScript 之外的 Web 编译目标。它使网站代码可以在安全的沙盒环境中以接近本机的速度运行。
它是根据所有主流浏览器(Chrome,Firefox,Safari 和 Edge)所代表的意见开发的,他们 达成了设计共识 ,这些浏览器现在都支持 WebAssembly。
WebAssembly 以二进制格式交付,这意味着与 JavaScript 相比,WebAssembly 在大小和加载时间上都具有优势。但是它也有易于理解的 文本表示形式 。
当 WebAssembly 首次发布时,一些开发人员认为它有可能最终取代 JavaScript 作为 Web 的主要语言。但是最好把 WebAssembly 看作是与现有 Web 平台良好集成的新工具,这是它的 高级目标 。
WebAssembly 并没有取代 JavaScript 现有的用例,而是吸引了更多人,因为它引入了新的用例。 目前 WebAssembly 还不能直接访问 DOM,大多数网站都希望使用 JavaScript,经过多年的优化,JavaScript 已经相当快了。以下 WebAssembly 可能的使用案例列表 的示例:
- 游戏
- 科学的可视化和模拟
- CAD应用
- 图像/视频编辑
这些应用共同特点是,它们通常会被看作是桌面应用。通过为 CPU 密集型任务提供接近本机的性能,WebAssembly 使得将这些程序迁移至 Web 成为可行。
现有网站也可以从 WebAssembly 中受益。 Figma( https://www. figma.com/ ) 提供了一个真实的例子,它通过使用 WebAssembly 大大缩短了其加载时间。如果网站使用进行大量计算的代码,则可以将其替换为 WebAssembly 以提高性能。
也许现在你对怎样使用 WebAssembly 感兴趣。你可以学习语言本身并 直接编写 ,但实际上它打算成为 其他语言的编译目标 。它被 设计 为对 C 和 C++ 具有良好的支持,Go语言 在 version 1.11 中增加了实验性支持 的版本中,Rust 也对其 进行了大量投入 。
但是也许你并不想为了使用 WebAssembly 而学习或使用其中某种语言。这就是 AssemblyScript 存在的意义。
AssemblyScript
AssemblyScript 是一个把 TypeScript 转换到 WebAssembly 的编译器。由微软开发的 TypeScript 将类型添加到了 JavaScript 中。它已经变得 相当受欢迎 ,即使对于不熟悉它的人,AssemblyScript 只允许 TypeScript 的有限功能子集,因此不需要花太多时间就可以上手。。
因为它与 JavaScript 非常相似,所以 AssemblyScript 使 Web 开发人员可以轻松地将 WebAssembly 整合到他们的网站中,而不必使用完全不同的语言。
试用
让我们编写第一个 AssemblyScript 模块(以下所有代码均可在 GitHub 上 找到)。我们需要 Node.js 的最低版本为 8 才能得到 WebAssembly 的支持 。
转到一个空目录,创建一个
package.json
文件,然后安装 AssemblyScript。请注意,我们需要直接从
它的 GitHub 存储库
安装。它尚未在 npm 上发布,因为 AssemblyScript 开发人员
还没有考虑编译器是否已经准备好
能够支持广泛使用。
mkdir assemblyscript-demo
cd assemblyscript-demo
npm init
npm install --save-dev github:AssemblyScript/assemblyscript
使用
asinit
命令生成脚手架文件:
npx asinit .
我们的
package.json
现在应该包含以下脚本:
{
"scripts": {
"asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --validate --debug",
"asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --validate --optimize",
"asbuild": "npm run asbuild:untouched && npm run asbuild:optimized"
}
顶层的
index.js
看起来像这样:
const fs = require("fs");
const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/optimized.wasm"));
const imports = {
env: {
abort(_msg, _file, line, column) {
console.error("abort called at index.ts:" + line + ":" + column);
Object.defineProperty(module, "exports", {
get: () => new WebAssembly.Instance(compiled, imports).exports
});
它使我们能够像使用普通的 JavaScript 模块一样轻松地
require
WebAssembly 模块。
assembly
目录中包含我们的 AssemblyScript 源代码。生成的示例是一个简单的加法函数。
export function add(a: i32, b: i32): i32 {
return a + b;
函数签名就像在 TypeScript 中那样,它之所以使用
i32
的原因是 AssemblyScript 使用了 WebAssembly 的
特定整数和浮点类型
,而不是 TypeScript 的href="
https://www.
typescriptlang.org/docs
/handbook/basic-types.html#number
">通用 number 类型。
让我们来构建示例。
npm run asbuild
build 目录现在应包含以下文件:
optimized.wasm
optimized.wasm.map
optimized.wat
untouched.wasm
untouched.wasm.map
untouched.wat
我们得到了构建的普通版本和优化版本。对于每个构建版本,都有一个
.wasm
二进制文件,一个
.wasm.map
源码映射
,以及二进制文件的
.wat
文本表示形式。文本表示形式是为了供人阅读,但现在我们无需阅读或理解它——使用 AssemblyScript 的目的之一就是我们不需要使用原始 WebAssembly。
启动 Node 并像其他模块一样使用编译模块。
$ node
Welcome to Node.js v12.10.0.
Type ".help" for more information.
> const add = require('./index').add;
undefined
> add(3, 5)
8
这就是从 Node 调用 WebAssembly 所需要的全部!
添加监视脚本
为了便于开发,我建议你在每次更改源代码时都用 onchange 自动重建模块,因为 AssemblyScript 尚不包括监视模式 。
npm install --save-dev onchange
在
package.json
中添加一个
asbuild:watch
脚本。包含
-i
flag
,即可在运行命令后立即运行初始构建。
{
"scripts": {
"asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --validate --debug",
"asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --validate --optimize",
"asbuild": "npm run asbuild:untouched && npm run asbuild:optimized",
"asbuild:watch": "onchange -i 'assembly/**/*' -- npm run asbuild"
}
现在你可以运行
asbuild:watch
,而不必不断地重新运行
asbuild
。
性能
让我们写一个基本的基准测试,用来了解究竟可以获得什么样的性能提升。 WebAssembly 的专长是处理诸如数字计算之类的 CPU 密集型任务,所以我们用一个函数来确定整数是否为质数。
我们的参考实现如下所示。这是一种幼稚的暴力解决方案,因为我们的目标是执行大量计算。
function isPrime(x) {
if (x < 2) {
return false;
for (let i = 2; i < x; i++) {
if (x % i === 0) {
return false;
return true;
等效的 AssemblyScript 版本仅需要一些类型注释:
function isPrime(x: u32): bool {
if (x < 2) {
return false;
for (let i: u32 = 2; i < x; i++) {
if (x % i === 0) {
return false;
return true;
}
我们将使用 Benchmark.js 。
npm install --save-dev benchmark
创建
benchmark.js
:
const Benchmark = require('benchmark');
const assemblyScriptIsPrime = require('./index').isPrime;
function isPrime(x) {
for (let i = 2; i < x; i++) {
if (x % i === 0) {
return false;
return true;
const suite = new Benchmark.Suite;
const startNumber = 2;
const stopNumber = 10000;
suite.add('AssemblyScript isPrime', function () {
for (let i = startNumber; i < stopNumber; i++) {
assemblyScriptIsPrime(i);
}).add('JavaScript isPrime', function () {
for (let i = startNumber; i < stopNumber; i++) {
isPrime(i);
}).on('cycle', function (event) {
console.log(String(event.target));
}).on('complete', function () {
const fastest = this.filter('fastest');
const slowest = this.filter('slowest');
const difference = (fastest.map('hz') - slowest.map('hz')) / slowest.map('hz') * 100;
console.log(`${fastest.map('name')} is ~${difference.toFixed(1)}% faster.`);
}).run();
在我的机器上,运行
node benchmark
时得到了以下结果:
AssemblyScript isPrime x 74.00 ops/sec ±0.43% (76 runs sampled)
JavaScript isPrime x 61.56 ops/sec ±0.30% (64 runs sampled)
AssemblyScript isPrime is ~20.2% faster.
请注意,这个测试是一个 microbenchmark ,我们应该谨慎阅读。
对于一些更多的 AssemblyScript 基准测试,我建议你查看 WasmBoy 基准测试 和 波动方程式基准测试 。
加载模块
接下来,在网站中使用我们的模块。
先创建
index.html
:
<!DOCTYPE html>
<meta charset="utf-8" />
<title>AssemblyScript isPrime demo</title>
</head>
<form id="prime-checker">
<label for="number">Enter a number to check if it is prime:</label>
<input name="number" type="number" />
<button type="submit">Submit</button>
</form>
<p id="result"></p>
<script src="demo.js"></script>
</body>
</html>
再创建
demo.js
。加载 WebAssembly 模块有
多种方式
,但是最有效的方法是通过使用
WebAssembly.instantiateStreaming
函数以流的方式编译和实例化。请注意,如果
assertion
失败的话,我们需要
提供 abort 函数
。
(async () => {
const importObject = {
env: {
abort(_msg, _file, line, column) {
console.error("abort called at index.ts:" + line + ":" + column);
const module = await WebAssembly.instantiateStreaming(
fetch("build/optimized.wasm"),
importObject
const isPrime = module.instance.exports.isPrime;
const result = document.querySelector("#result");
document.querySelector("#prime-checker").addEventListener("submit", event => {
event.preventDefault();
result.innerText = "";
const number = event.target.elements.number.value;
result.innerText = `${number} is ${isPrime(number) ? '' : 'not '}prime.`;