第 1 章 初识WebAssembly

本章内容

● WebAssembly是什么

● WebAssembly能解决什么问题

● WebAssembly的工作原理

● WebAssembly的安全性来自何处

● 哪些语言可用于创建WebAssembly模块

提到Web开发,大多数开发者最关心的一件事就是性能——从网页加载速度到整体的响应性。若干研究表明,如果网页不能在3秒内完成加载,那么40%的访问者就会离开。这个百分比会随着网页加载秒数的增加而增加。

网页加载时间并不是唯一的问题。根据一篇谷歌论文所述,如果某个网页的性能很差,那么会有79%的访问者声称他们不太可能再次访问该网站(参见Daniel An与Pat Meenan于2016年7月合著的文章“Why marketers should care about mobile page speed?”)。

随着Web技术的发展,越来越多的应用程序转移到Web上。这就向开发者提出了另一项挑战,因为Web浏览器只支持一种编程语言:JavaScript。

从某种意义上说,在所有浏览器上只使用一种编程语言是好的——只需要编写代码一次,就可以确保它能在所有浏览器上运行。但仍需要在想要支持的每个浏览器上测试代码,因为有时各个厂商的实现方式会略有不同。另外,有时一个浏览器厂商并不与其他厂商同时添加某项新功能。总体上说,只支持一种语言比支持四五种语言简单一些。但浏览器只支持JavaScript的缺点是,我们想要移植到Web上的应用程序并不是用JavaScript编写的,而是用C++这样的语言编写的。

JavaScript是一种很棒的编程语言,但现在我们要求它做的已经超出其本来的设计意图(比如游戏所需要的密集型计算),并且还要求它能够快速执行。

1.1 WebAssembly是什么

随着浏览器开发商寻找提高JavaScript性能的方法,Mozilla(Firefox浏览器开发者)定义了一个名为asm.js的JavaScript子集。

1.1.1 WebAssembly的先驱:asm.js

asm.js具有以下优势。

● 你并不直接编写asm.js,而是用C或C++编写逻辑,然后将其转换为JavaScript。将代码从一种语言转换到另一种语言的过程称为transpiling

● 大计算量代码可以更快地执行。当浏览器的JavaScript引擎看到名为asm pragma语句的特殊字符串("use asm";)时,其作用相当于一个标记,以此告诉浏览器它可以使用底层系统操作而不是更昂贵的JavaScript操作。

从第一次调用就可以获得更快的代码执行速度。包含的类型提示可以告知JavaScript一个变量会持有何种类型的数据。比如,可以用a|0来提示变量a将持有一个32位整型值。这种方法很有效,因为0的位OR运算不会改变原始值,所以这么做不会产生副作用。

这些类型提示作为一个承诺向JavaScript引擎表明,如果代码将一个变量声明为整型,那么它永远不会被修改,比如不会改为字符串。因此,JavaScript引擎不需要监测代码来确定这些类型,而是可以像代码所声明的那样直接编译它。

以下代码片段展示了一个asm.js代码示例。

function AsmModule() {
  "use asm";    ←---- 用于通知JavaScript后续代码为asm.js的标记
  return {
    add: function(a, b) {
      a = a | 0;    ←---- 类型提示表明参数为32位整型
      b = b | 0;
      return (a + b) | 0;    ←---- 类型提示表明返回值为32位整型
    }
  }
}

虽然具有以上优点,但asm.js仍有一些缺点。

● 所有这些类型提示可能会使文件非常大。

● 因为asm.js文件是JavaScript文件,所以它仍然需要由JavaScript引擎读入和解析。在像手机这样的设备上,这会是个问题,因为所有处理过程延长了加载时间,而且会消耗电量。

● 为了添加新特性,浏览器厂商将不得不修改JavaScript语言本身,而这并不是我们所期望发生的。

● JavaScript是一种编程语言,并没有设计用来作为编译目标。

1.1.2 从asm.js到MVP

浏览器厂商关注改进asm.js的方法,他们想出了一个WebAssembly最小化可行产品(minimum viable product,MVP),其目标是保留asm.js的优点,同时解决其缺点。2017年,4个主流浏览器厂商(谷歌、微软、Apple和Mozilla)都更新了自己的浏览器,以提供对此MVP(有时也称为Wasm)的支持。

● WebAssembly是一种底层类汇编语言,能够在所有当代桌面浏览器及很多移动浏览器上以接近本地的速度运行。

● WebAssembly文件设计得很紧凑,因此可以快速传输和下载。这些文件的设计方式也使得它们可以快速解析和初始化。

● WebAssembly被设计为编译目标,因此用C++、Rust和其他语言编写的代码现在可以在Web上运行了。

后端开发者可以利用WebAssembly来提高代码复用度或者无须重写就将自己的代码移植到Web中。Web开发者也可以从创建新库、改进现有库,以及提高自己代码中大计算量部分的性能中获益。尽管WebAssembly主要用于Web浏览器,但其设计也考虑到了可移植性,因此也可以在浏览器之外使用。

1.2 WebAssembly解决了哪些问题

WebAssembly MVP解决了asm.js的以下问题。

1.2.1 性能改进

WebAssembly致力于解决的最大问题之一是性能问题——从代码的下载时间到代码的执行速度。使用编程语言,而不是编写计算机处理器理解的机器语言(1和0,或者本地代码)时,你通常会编写更接近于人类语言的某种东西。尽管使用从计算机细节中抽象出来的代码更容易,但计算机处理器并不理解你的代码,因此运行时需要将你编写的内容转换为机器码。

JavaScript是一种解释型编程语言,也就是说,它会在执行时读入你编写的代码,并将这些指令即时翻译为机器码。使用解释型语言时,不需要提前编译代码,这意味着它启动的速度更快。但缺点是,解释器必须在每次运行代码时将指令转换为机器码。举例来说,如果你的代码在执行一个循环,那么每次执行该循环时,循环的每一行都要被解释。因为解释过程并不总有大量时间可用,所以并不总能进行优化。

其他编程语言(如C++)并不是解释型的。使用这类语言时,需要利用称为编译器的特定程序预先将指令转换为机器码。使用编译型编程语言时,需要一些时间将指令转换为机器码,然后才能运行它们,但其优点是有更多时间来优化代码的执行;一旦指令编译为机器码,就不需要再次编译。

随着时间的发展,JavaScript已经从简单连接多个组件的胶水语言(那时预计其生存期很短)发展为大量网站用于执行复杂处理的语言,它很容易涉及成百上千行代码,而且,随着单页应用程序的兴起,其代码通常生存期很长。互联网已经从只是展示文本和少量图片的网站发展为具有强交互性的网站,甚至是称为Web应用程序的站点,因为它们类似于桌面应用程序,只不过运行于Web浏览器中。

随着开发者持续挑战JavaScript的极限,一些引人注意的性能问题开始显现出来。浏览器厂商决定要找到一个折中点,不仅可以获得解释器的优点,代码被调用时能够尽快启动,而且拥有执行时能更快速运行的代码。为了让代码更快,浏览器厂商引入了一个称为JIT(just-in-time,即时)编译的概念,JavaScript引擎在运行时监测代码。如果某一部分代码被使用的次数足够多,那么引擎就会试图将这一部分编译为机器码,这样它就可以绕过JavaScript引擎,转而使用底层系统方法,这要快得多。

JavaScript引擎需要监测代码多次才能将其编译为机器码,因为JavaScript也是一种动态编程语言。在JavaScript中,一个变量可以持有任何类型的值。举例来说,一个变量可能在最初持有一个整型值,但之后被赋予一个字符串。代码运行若干次之后,浏览器才能了解应该预期什么(类型)。即使是在编译后,也仍然需要监测这段代码,因为某些条件可能会改变,此时需要抛弃这部分编译后的代码,重新开始整个处理过程。

1.2.2 比JavaScript更快的启动速度

和asm.js一样,WebAssembly不是设计用于手动编写的,也不是供人类阅读的。代码被编译为WebAssembly之后,字节码会以二进制格式而不是文本格式表示,这可以减小文件大小,从而支持快速传输和下载。

这个二进制文件的设计方式使得模块验证可以在一轮内完成,其结构也支持并行编译文件的不同部分。

通过实现JIT编译,浏览器厂商在提高JavaScript性能方面获得了巨大进步。但是JavaScript引擎只能在监测代码若干次后才将其编译为机器码。另外,WebAssembly代码是静态类型的,即可以预知变量持有的值的类型。WebAssembly代码可以从一开始就编译为机器码,无须先监测,因此第一次运行代码就可以看到性能提升。

自MVP首次发布以来,浏览器厂商已经通过不同方式提升WebAssembly的性能。其中一种方式是引入了一种称为流编译的技术,在浏览器下载和接收文件时,该技术可以将WebAssembly代码编译为机器码。流编译支持WebAssembly模块下载完毕即进行初始化,这样会显著加速模块的启动过程。

1.2.3 可以在浏览器中使用JavaScript之外的语言

目前为止,要想在Web上运行非JavaScript语言,需要将代码转换为JavaScript,但后者并未被设计为编译目标。而WebAssembly从一开始就被设计为编译目标,因此,如果开发者想要使用某种特定的语言进行Web开发,无须将代码转译为JavaScript就可以实现。

因为WebAssembly没有绑定到JavaScript语言,所以这项技术更容易改进,而无须担心会影响JavaScript。这种独立性促使WebAssembly具备大幅提升性能的能力。

对于WebAssembly MVP来说,C和C++是目标为WebAssembly的重点语言,但Rust也对此增加了支持,并且几种其他语言也在试验对它的支持。

1.2.4 代码复用的机会

能够用非JavaScript语言编写代码,并将其编译为WebAssembly,对于代码复用来说,这为开发者提供了更多灵活性。过去不得不用JavaScript重写的东西现在可以直接在桌面或服务器上使用,并在浏览器中运行。

1.3 WebAssembly的工作原理

如图1-1所示,使用JavaScript时,代码被包含在网站中并在运行时解释。因为JavaScript变量是动态的,所以观察图示中的函数add可以发现,当前处理的变量为何种类型并不是显而易见的。变量a和b可能是整型、浮点型、字符串,甚至是它们的组合,比如一个变量是字符串,而另一个是浮点型。

图1-1 JavaScript在执行过程中被编译为机器码

确定类型的唯一方法就是在代码执行时进行监测,这也正是JavaScript引擎所做的。一旦获得了这些变量的类型,引擎就可以将这段代码转换为机器码。

WebAssembly不被解释,而是由开发者提前编译为WebAssembly二进制格式,如图1-2所示。由于变量类型都是预知的,因此浏览器加载WebAssembly文件时,JavaScript引擎无须监测代码。它可以简单地将这段代码的二进制格式编译为机器码。

图1-2 C++转换为WebAssembly,然后在浏览器中转换为机器码

1.3.1 编译器工作原理概览

1.2.1节简单讨论过这一点,开发者以更接近于人类语言的语言编写代码,但计算机处理器只能理解机器语言。因此,你编写的代码必须转化为机器码才能运行。前面没有提到的是,每一类计算机处理器都有它自己的机器码类型。

如果将每种编程语言都直接编译为机器码的各个版本,那么效率会很低。取而代之的是,如图1-3所示,编译器中称为前端的部分会将你所编写的代码编译为一种中间表示(intermediate representation,IR)。创建好IR代码后,编译器的后端部分会接收IR代码,对其进行优化,然后将其转换为所需要的机器码。

图1-3 编译器的前端和后端

由于浏览器可以在若干不同的处理器(比如,从桌面计算机到智能手机和平板设备)上运行,因此为每个可能的处理器发布一个WebAssembly代码的编译后版本会非常繁复。图1-4展示了替代方法,即取得IR代码,并通过一个专门的编译器来运行,这个编译器将IR代码转换为一种专用字节码并放入后缀为.wasm的文件中。

图1-4 编译器前端与WebAssembly后端合作

Wasm文件中的字节码还不是机器码,它只是支持WebAssembly的浏览器能够理解的一组虚拟指令。如图1-5所示,当加载到支持WebAssembly的浏览器中时,浏览器会验证这个文件的合法性,然后这些字节码会继续编译为浏览器所运行的设备上的机器码。

图1-5 Wasm文件加载到浏览器中,然后被编译为机器码

1.3.2 模块的加载、编译和实例化

本书撰写时,下载Wasm文件到浏览器中并让浏览器编译它的过程是通过JavaScript函数调用完成的。我们期望未来会允许WebAssembly模块与ES6模块交互,这将会支持通过一个专门的HTML标签(< script type="module">)来加载WebAssembly模块,但目前还不支持这种形式。(ES是ECMAScript的简称,6是版本号。ECMAScript是JavaScript的官方名称。)

在编译这个模块的二进制字节码前,需要先验证WebAssembly模块的合法性,以确保这个模块结构正确,从而确保代码不会做任何不允许它做的事情,也不会访问这个模块不能访问的内存。检查也是在运行时进行的,以确保代码位于它可以访问的内存中。Wasm文件的组织结构使得验证可以单轮完成,这样可以确保验证过程、编译为机器码,以及之后的实例化过程尽快完成。

浏览器将WebAssembly字节码编译为机器码后,就可以将编译后的模块传送给一个Web worker(第9章将深入讨论Web worker,目前只需要了解Web worker是一种在JavaScript中创建线程的方式)或另一个浏览器窗口。甚至可以用这个编译后的模块创建这个模块的更多实例。

编译后,Wasm文件还需要进行实例化才能使用。实例化的过程包括接收需要的所有导入对象,初始化模块元素,如果定义了启动函数,那么还要调用启动函数,最终向执行环境返回一个模块实例。

WebAssembly与JavaScript

目前为止,允许在JavaScript虚拟机(virtual machine,VM)中运行的唯一语言是JavaScript。多年来,在试验其他技术(如插件)时,它们需要创建自己的沙箱VM,这扩大了攻击目标,也耗费了计算机资源。JavaScript VM首次开放自己,允许WebAssembly代码也运行在同一个VM中。这带来了几点优势,其中最大的优势是,VM的安全性多年来已经被充分测试和强化过。如果创建一个新VM,那么它肯定会有一些安全性问题需要解决。

WebAssembly被设计为JavaScript的一个组件,而不是替代品。尽管我们很可能会看到有些开发者试图只用WebAssembly来创建整个网站,但这可能不会是普遍情况。一些情况下,JavaScript仍然是更好的选择。另一些情况下,网站可能需要包含WebAssembly来进行快速计算或提供底层支持。比如,有几个浏览器将SIMD(single instruction, multiple data,单指令,多数据),即用单条指令处理多数据的能力,构建到了JavaScript之中,但浏览器厂商决定弃用其JavaScript实现,只通过WebAssembly模块来提供SIMD支持。因此,如果网站需要SIMD支持,那么就需要包含一个WebAssembly模块来与之通信。

为Web浏览器编程时,基本上有两个主要组件:JavaScript VM(WebAssembly模块运行于其中)以及Web API(比如DOM、WebGL、Web worker等)。作为MVP,WebAssembly是缺乏某些东西的。WebAssembly模块可以与JavaScript通信,但是还不能与任何Web API直接交流。现在正在开发一项后MVP特性,该特性将允许WebAssembly直接访问Web API。目前,模块可以调用JavaScript与Web API交流,让JavaScript代表模块执行所需要的动作。

1.4 WebAssembly模块的结构

目前WebAssembly只能使用4种值类型:

● 32位整型

● 64位整型

● 32位浮点型

● 64位浮点型

布尔值用32位整型表示,0为false,非0值为true。所有其他值类型(如字符串)需要在模块的线性内存空间中表示。

WebAssembly程序的主要单元称为模块,这个术语既用来表示代码的二进制版本,也表示浏览器中的编译后版本。你并不需要手动创建WebAssembly模块,但是对模块结构及其底层工作原理有一定了解是有所帮助的,因为在模块的初始化过程和整个生存期内,你都要与它的某些方面交互。

图1-6是一个WebAssembly文件结构的基本表示。第2章将进一步介绍模块的结构,目前先进行简单的概述。

图1-6 WebAssembly文件结构的基本表示

Wasm文件以一个名为前导(preamble)的段开始。

1.4.1 前导

前导中包含一个幻数(magic number,0x00 0x61 0x73 0x6D,即\0asm),用于区分WebAssembly模块与ES6模块。这个幻数之后是版本号(0x01 0x00 0x00 0x00,即1),以指明创建本文件时使用的WebAssembly二进制格式的版本。

目前这个二进制格式只有一个版本。WebAssembly的目标之一是在引入新特性的同时保持一切向后兼容,避免不得不增加版本号的情况。如果出现了必须破坏现有内容来实现的特性,那么就得递增版本号。

前导之后,模块可以有若干个,但每一段都是可选的,因此严格来说可以存在没有任何段的空模块。第3章会介绍空模块的一个用例,它可以在判断某个Web浏览器是否支持WebAssembly时发挥作用。

可用的段分为两类:已知段自定义段

1.4.2 已知段

已知段只能被包含一次,并且要按照特定顺序出现。每个已知段都有良好的定义、专门的用途,进行模块初始化时会检验其有效性。第2章会深入介绍已知段。

1.4.3 自定义段

自定义段为用户提供了在模块内包含数据的一种方法,应用于已知段不适用的情况。自定义段可以出现在模块的任意位置(已知段的前、后或之间)任意多次,多个自定义段甚至可以复用同一个名字。

与已知段不同,如果某个自定义段的布局不正确,那么将不会触发验证错误。框架可以惰性加载自定义段,也就是说,它们包含的数据可能直到模块初始化到某个阶段才有效。

WebAssembly MVP有一个名为“name”的自定义段。这个段背后的思路是,WebAssembly模块可以有一个调试版本,在调试时,这个段会持有文本形式的函数和变量名。与其他自定义段不同,这个段应该只出现一次,并且只出现在Data段之后。

1.5 WebAssembly文本格式

WebAssembly的设计并没有忘记Web的开放性,其二进制格式不是设计供人类读写的,但并不能因此就认为WebAssembly模块是开发者试图隐藏代码的一种方式。实际上,恰好相反。开发者为WebAssembly定义了一种使用s-表达式的文本格式,以对应二进制格式。

信息 符号表达式(或s-表达式)是为Lisp编程语言发明的。一个s-表达式可以是一个原子或一个s-表达式的有序对,后者支持s-表达式的嵌套。原子是一个非列表的符号,比如foo或23。列表用括号表示,可以是空的,也可以持有原子或其他列表;成员之间用空格分隔,比如()、(foo),以及(foo (bar 132))。

举例来说,这个文本格式允许浏览器中的代码支持View Source,也可以用于调试。甚至可以手动编写s-表达式,然后用专门的编译器将代码编译为WebAssembly二进制格式。

因为选择View Source以及用于调试目的时浏览器会使用WebAssembly文本格式,所以对文本格式有基本了解是有用的。比如,既然模块的所有段都是可选的,那么可以使用下列s-表达式定义一个空模块。

(module)

如果要将s-表达式(module)编译为WebAssembly二进制格式,并观察得到的二进制值,那么这个文件会只包含前导字节:0061 736d(幻数)和0100 0000(版本号)。

预告 第11章将只用文本格式创建一个WebAssembly模块,这样一来,查看其内容时(比如需要在浏览器中调试模块时),你可以更好地理解。

1.6 WebAssembly如何获得安全性

WebAssembly的安全性来源之一是,它是第一个共享JavaScript VM的语言,而JavaScript VM在运行时是沙箱化的,同时也经历了多年的检验和安全测试,这确保了其安全性。WebAssembly模块的可访问范围不超过JavaScript的访问范围,同时也会遵守相同的安全性规则,包括同源策略(same-origin policy)这样的增强规则。

与桌面应用程序不同,WebAssembly模块对设备内存没有直接访问权限,而是运行时环境在初始化过程中向模块传递一个ArrayBuffer。模块将这个ArrayBuffer当作线性内存来使用,WebAssembly框架执行检查以确保代码不会对这个数组进行越界操作。

对于像函数指针这样存储在Table段中的项目,WebAssembly模块也不能直接访问。代码会用索引值向WebAssembly框架提出访问某个项目的请求。然后框架访问内存,并代表代码执行这个项目。

在C++中,执行栈与线性内存一起位于内存中,虽然C++代码不应该修改执行栈,但是它可以使用指针实现修改。WebAssembly的执行栈与线性内存是分离的,代码无法访问。

更多信息 要想了解关于WebAssembly安全性模型的更多信息,可以访问WebAssembly官方网站。

1.7 哪些语言可用来创建WebAssembly模块

为了创建这个MVP,WebAssembly的最初关注点在C和C++语言上,但后来Rust和AssemblyScript这样的语言也增加了支持。也可以使用WebAssembly文本格式通过s-表达式编写代码,然后用专门的编译器将其编译为WebAssembly。

现在,WebAssembly的MVP还没有垃圾回收(garbage collection,GC),这限制了某些语言的使用。GC作为一种后MVP功能正在开发中,但在其实现之前,有几种语言正在试验WebAssembly支持,方式是将自己的VM编译到WebAssembly,或者在某些情况下将自己的垃圾回收器包含进去。

以下语言正在试验或已经拥有WebAssembly支持。

● C和C++。

● Rust正致力于成为WebAssembly的首选编程语言。

● AssemblyScript是一种新编译器,它接受TypeScript并将其转换为WebAssembly。考虑到TypeScript是带类型的并且已经可以转译到JavaScript,转换它是有意义的。

● TeaVM是一个将Java转译到JavaScript的工具,现在也可以生成WebAssembly了。

● Go 1.11为WebAssembly增加了一个试验性项目,其编译后的WebAssembly模块包含一个垃圾回收器。

● Pyodide是Python的一个项目,其中包含了Python科学栈的核心包:Numpy、Pandas和matplotlib。

● Blazor是微软的实验性项目,用于将C#引入WebAssembly。

更多信息 GitHub网站上维护了一个语言列表(Awesome WebAssembly Language),其中的语言可以编译到WebAssembly,或者将其VM放入WebAssembly。这个列表也指明了这些语言对WebAssembly的支持程度。

本书将使用C和C++语言来学习WebAssembly。

1.8 我的模块可以用在何处

2017年,所有的现代浏览器厂商都发布了支持WebAssembly MVP的浏览器,其中包括Chrome、Edge、Firefox、Opera和Safari。一些移动Web浏览器也支持WebAssembly,其中包括Chrome、Android Firefox和Safari。

正如本章开头所述,WebAssembly在设计时就考虑了可移植性,因此它可以用于多个场合,而不限于浏览器。一个名为WASI(WebAssembly Standard Interface,WebAssembly标准接口)的新标准正在开发之中,它确保了WebAssembly模块可以在所有受支持系统上保持一致性。以下文章对WASI进行了很好的概述:Lin Clark撰写的“Standardizing WebAssemblySI: A system interface to run WebAssembly outside the Web”(2019年3月27日)。

更多信息 如果想深入学习WASI,可以在GitHub网站上找到相关链接和文章的索引列表(Awesome WASI)。

从版本8开始,Node.js就是支持WebAssembly模块的一个非浏览器环境。Node.js是一个JavaScript运行时,由Chrome的V8 JavaScript引擎构建,支持在服务器端使用JavaScript代码。很多开发者将WebAssembly看作在浏览器(而不是JavaScript)中使用他们熟悉的代码的机会,与之类似,Node.js让喜欢JavaScript的开发者也可以在服务器端使用它。为了展示WebAssembly在浏览器之外的使用,第10章将介绍如何在Node.js中使用WebAssembly模块。

WebAssembly并不是JavaScript的替代品,而是它的一个补充。有些情况下,使用WebAssembly模块是更好的选择,有些情况下则更应该使用JavaScript。与JavaScript在同一个VM中运行可以让两种技术利用彼此。

WebAssembly为熟练使用非JavaScript的开发者开启了一扇门,以帮助他们在Web中使用自己的代码。它也让不了解如何编写像C或C++ 这样的代码的Web开发者可以访问更新、更快的库,以及那些拥有当前JavaScript库不支持的功能的库。某些情况下,WebAssembly可以被一些库用于加速库的某些部分的执行速度。除了拥有更快的代码之外,这个库的工作方式与通常无异。

关于WebAssembly最令人激动的一点是,在所有主流桌面浏览器、几个主要移动浏览器,甚至浏览器之外的Node.js之中,它都是可用的。

1.9 小结

如本章所述,WebAssembly实现了若干性能改进,以及对语言选择和代码复用方面的改进。WebAssembly带来的几个关键改进如下。

● 传输和下载更快,因为二进制编码使得文件更小。

● 鉴于Wasm文件的结构,它们可以被快速解析和验证,同时文件的各个部分可以并行编译。

● 通过流编译技术,可以在下载WebAssembly模块的同时编译它,这样一来,下载完毕时就可以实例化这个模块,从而大大加速加载过程。

● 对于计算这样的功能来说,代码可以更快地执行,因为使用了机器级调用,而不是更昂贵的JavaScript引擎调用。

● 编译前不需要检测代码以确定其行为方式。结果是代码每次运行的速度都相同。

● 与JavaScript分离,可以更快地改进WebAssembly,因为这不会影响JavaScript语言。

● 可以在浏览器中使用非JavaScript语言编写的代码。

● 通过改变WebAssembly的框架结构,实现其在浏览器内外部的使用,从而增加代码复用的机会。