相关文章推荐
坐怀不乱的啄木鸟  ·  mysql ...·  7 月前    · 
飞奔的柚子  ·  Python ...·  1 年前    · 
乐观的松球  ·  .Net ...·  1 年前    · 
14
作者:Danny Guo

翻译:疯狂的技术宅

原文: https://blog.logrocket.com/th...

未经允许严禁转载

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 的通用 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)
    

    这就是从 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.`;
    })();

    现在安装 static-server。因为要使用WebAssembly.instantiateStreaming,我们需要创建服务,该模块需要使用 MIME typeapplication/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。

    本文首发微信公众号:前端先锋

    欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

    欢迎继续阅读本专栏其它高赞文章:

  • 深入理解Shadow DOM v1
  • 一步步教你用 WebVR 实现虚拟现实游戏
  • 13个帮你提高开发效率的现代CSS框架
  • 快速上手BootstrapVue
  • JavaScript引擎是如何工作的?从调用栈到Promise你需要知道的一切
  • WebSocket实战:在 Node 和 React 之间进行实时通信
  • 关于 Git 的 20 个面试题
  • 深入解析 Node.js 的 console.log
  • Node.js 究竟是什么?
  • 30分钟用Node.js构建一个API服务器
  • Javascript的对象拷贝
  • 程序员30岁前月薪达不到30K,该何去何从
  • 14个最好的 JavaScript 数据可视化库
  • 8 个给前端的顶级 VS Code 扩展插件
  •