做者:Danny Guo翻译:疯狂的技术宅javascript
原文:https://blog.logrocket.com/th...html
未经容许严禁转载前端
WebAssembly(Wasm)是 Web 浏览器中相对较新的功能,但它地扩展了把 Web 做为服务应用平台的功能潜力。java
对于 Web 开发人员来讲,学习使用 WebAssembly 可能会有一个艰难的过程,可是 AssemblyScript 提供了一种解决方法。首先让咱们看一下为何 WebAssembly 是一项颇有前途的技术,而后再看怎样 AssemblyScript 挖掘潜力。node
WebAssembly 是浏览器的低级语言,为开发人员提供了除 JavaScript 以外的 Web 编译目标。它使网站代码能够在安全的沙盒环境中以接近本机的速度运行。c++
它是根据全部主流浏览器(Chrome,Firefox,Safari 和 Edge)所表明的意见开发的,他们达成了设计共识,这些浏览器如今都支持 WebAssembly。git
WebAssembly 以二进制格式交付,这意味着与 JavaScript 相比,WebAssembly 在大小和加载时间上都具备优点。可是它也有易于理解的文本表示形式。程序员
当 WebAssembly 首次发布时,一些开发人员认为它有可能最终取代 JavaScript 做为 Web 的主要语言。可是最好把 WebAssembly 看做是与现有 Web 平台良好集成的新工具,这是它的高级目标。github
WebAssembly 并无取代 JavaScript 现有的用例,而是吸引了更多人,由于它引入了新的用例。 目前 WebAssembly 还不能直接访问 DOM,大多数网站都但愿使用 JavaScript,通过多年的优化,JavaScript 已经至关快了。如下 WebAssembly 可能的使用案例列表的示例:golang
这些应用共同特色是,它们一般会被看做是桌面应用。经过为 CPU 密集型任务提供接近本机的性能,WebAssembly 使得将这些程序迁移至 Web 成为可行。
现有网站也能够从 WebAssembly 中受益。 Figma(https://www.figma.com/) 提供了一个真实的例子,它经过使用 WebAssembly 大大缩短了其加载时间。若是网站使用进行大量计算的代码,则能够将其替换为 WebAssembly 以提升性能。
也许如今你对怎样使用 WebAssembly 感兴趣。你能够学习语言自己并直接编写,但实际上它打算成为其余语言的编译目标。它被设计为对 C 和 C++ 具备良好的支持,Go语言在 version 1.11 中增长了实验性支持的版本中,Rust 也对其进行了大量投入。
可是也许你并不想为了使用 WebAssembly 而学习或使用其中某种语言。这就是 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 的通用 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> <html> <head> <meta charset="utf-8" /> <title>AssemblyScript isPrime demo</title> </head> <body> <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.`; }); })();
如今安装 static-server。由于要使用WebAssembly.instantiateStreaming
,咱们须要建立服务,该模块须要使用 MIME type 的 application/wasm
。
npm install --save-dev static-server
将脚本添加到 package.json
中。
{ "scripts": { "serve-demo": "static-server" } }
运行 npm run serve-demo
并在浏览器中打开 localhost URL。提交表单中的数字,你将收到一条消息,指出该数字是否为素数。如今,咱们已经实现了从用 AssemblyScript 编码到在网站中实际使用的整个过程。
WebAssembly 以及经过 AssemblyScript 的扩展,不会使每一个网站都神奇地变得更快,可是这并不重要。 WebAssembly 之因此使人兴奋,是由于它可使更多的应用在 Web 变得中可行。
相似地,AssemblyScript 使更多开发人员可使用 WebAssembly,这使咱们很容易默认使用 JavaScript,可是当须要大量运算工做时,能够用 WebAssembly。