1、前言javascript
自从JavaScript诞生开始,到如今开始变成流行的编程语言,背后的是web发展所推进的。web应用的变得更多更复杂,可是渐渐暴露出JavaScript的问题:html
(1)语法太灵活致使开发大型web项目困难;前端
(2)性能不足知足一些场景的须要。java
2、为何须要WebAssemblynode
针对以上的问题,JavaScript出现了一些代替语言,好比:react
(1)微软的TypeScript经过JS加入静态类型检查机制来改进js松散的语法,提高代码健壮性。webpack
(2)谷歌的Dart则是为浏览器引入新的虚拟机去直接运行Dart程序以提高性能。c++
(3)火狐的asm.js则是取JS的子集,JS引擎针对asm.js作性能优化。git
以上尝试各有优缺点。其中:github
(1)TypeScript只是解决JS语法松散的问题,最后仍是须要编译成JS去运行,对性能没有提高。
(2)Dart只能在chrome预览版中运行,无主流浏览器支持,用Dart开发的人很少。
(3)asm.js语法太简单,有很大限制,开发现率低。
三大浏览器巨头分别提出了本身的解决方案,互补兼容,这违背了web的宗旨,是技术规范统一让web走到今天,所以若是有一套新的规范去解决JS面临的问题就太好了。
因而webAssembly出现了,webAssembly是一种新的字节码格式,主流的浏览器已经支持webAssembly。和JS须要解释执行不一样的是,webAssembly字节码和底层机器码很类似可快速装载运行,所以性能相对于JS解释执行大大提高。也就是说webAssembly并非一门编程语言,而是一份字节码标准,须要用高级语言编译出字节码放到webAssembly虚拟机中才能运行,浏览器厂商须要作的是根据webAssembly规范实现虚拟机。
3、webAssembly原理
要弄清楚webAssembly原理,须要先搞清计算机运行原理。
电子计算机是由电子元件组成,为了方便处理电子元件只存在开闭两种状态,对于1和0,也就是计算机只认识1和0,数据和逻辑都须要由1和0表示,也就是能够直接装载到计算机中运行的机器码。机器码可读性极差,所以人们经过高级语言c,c++,Java,Go等编写再编译成机器码。
因为不一样的计算机CPU架构不一样,机器码标准有所差异,常见的CPU架构包括X86,AMD64,ARM,所以在由高级编程语言编程成可自行代码时须要指定目标架构。
webAssembly字节码是一种抹平了不一样架构的机器码,webAssembly字节码不能直接在任何一种CPU架构上运行,可是因为很是接近机器码,能够很是快的被翻译为对应架构的机器码,所以webAssembly运行速度接近机器码,这听上去很是像java字节码。
相对于JS,webAssembly有如下优势:
(1)体积小。因为浏览器运行时只加载编译成的字节码,同样的逻辑比字符串描述的JS文件体积小不少。
(2)加载快。因为文件体积小,再加上无需解释执行,webAssembly能更快的加载并实例化,减小运行前的等待时间。
(3)兼容问题少。webAssembly是很是底层的字节码规范,制定好之后不多变更,就算发生变化,只须要从高级语言编译成字节码过程作兼容。可能出现兼容问题是JS和webAssembly桥接的JS接口。
每个高级语言都去实现源码到不一样平台的机器码的转换工做是重复的,高级语言只须要生成底层虚拟机(LLVM)认识的中间语言(LLVM IR),LLVM能实现:
(1)LLVM IR到不一样CPU架构机器码的生成。
(2)机器码编译时性能和大小优化。
除此以外LLVM还实现了LLVM IR到webAssembly字节码的编译功能,也就是说只要高级语言能转换成LLVM IR,就能编译成webAssembly字节码,目前能编译webAssembly字节码的高级语言有:
(1)AssemblyScript:语法和TypeScript一致,对前端来讲学习成本低,为前端编写webAssembly最佳选择。
(2)c\c++:官方推荐的方式,详细见文档。
(3)Rust:语法复杂,学习成本高,对于前端来讲可能会不适应。详细见使用文档。
(4)kotlin:语法和java,js很类似,语言学习成本低,详细见文档。
(5)GoLang:语法简单,学习成本低。可是对于webAssembly还处于未正式发布阶段,详细见文档。
一般负责把高级语言翻译到 LLVM IR 的部分叫作编译器前端,把 LLVM IR 编译成各架构 CPU 对应机器码的部分叫作编译器后端; 如今愈来愈多的高级编程语言选择 LLVM 做为后端,高级语言只需专一于如何提供开发效率更高的语法同时保持翻译到 LLVM IR 的程序执行性能。
4、编写webAssembly
4.1 为何选择AssemblyScript做为webAssembly的开发语言
AssemblyScript相对于C,rust等其余语言去写webAssembly而言,好处就是:对于前端来讲无需额外的新语言学习成本,还有对于不支持webAssembly的浏览器,能够经过TypeScript编译器编译成能够正常执行的JS代码。从而实现从JS到webAssembly的平滑迁移。
4.2接入webpack构建
任何新的web开发技术都少不了构建,为了提供一套流畅的webAssembly开发流程,接下来开始介绍webpack具体步骤。
一、安装依赖,以便TS源码被AssemblyScript编译成webAssembly。
{
"devDependencies": { "assemblyscript": "github:AssemblyScript/assemblyscript", "assemblyscript-typescript-loader": "^1.3.2", "typescript": "^2.8.1", "webpack": "^3.10.0", "webpack-dev-server": "^2.10.1" } }
二、修改webpack.config.js,加入loader。
module.exports = { module: { rules: [ { test: /\.ts$/, loader: 'assemblyscript-typescript-loader', options: { sourceMap: true, } } ] }, };
三、修改typeScript的编译器配置tsconfig.json,以便让typeScript编译器能支持AssemblyScript中引入内置类型和函数。
{
"extends": "../../node_modules/assemblyscript/std/portable.json", "include": [ "./**/*.ts" ] }
四、配置直接继承自AssemblyScript内置的配置文件。
5、webAssembly相关工具
除了上面提到的webAssembly二进制工具箱,webAssembly社区还有如下经常使用工具:
(1)Emscripten:能把c,c++代码转换成wasm,asm.js。
(2)Binaryen:提供更简单的IR,把IR转换成wasm,而且提供wasm的编译时优化,wasm虚拟机,wasm压缩等功能,前面提到的AssemblyScript就是基于他的。
6、webAssembly JS API
目前webAssembly只能经过js去加载和执行,可是将来浏览器中能够经过像加载JS那样去加载和执行webAssembly,下面介绍如何使用JS调用webAssembly。
JS 调 WebAssembly 分为 3 大步:加载字节码 > 编译字节码 > 实例化,获取到 WebAssembly 实例后就能够经过 JS 去调用了,以上 3 步具体的操做是:
一、对于浏览器能够经过网络请求去加载字节码,对于nodejs能够经过fs模块读取字节码文件;
二、在获取到字节码后都须要转换成ArrayBuffer后才能被编译,经过webAssembly经过JS API webAssembly.compile编译后会经过Promise resolve 一个webAssembly.module,这个module是不能直接被调用的须要。
三、在获取到module后须要经过webAssembly.instance API去实例化module,获取到instance后就能够像使用JS模块一个调用。
其中的第 二、3 步能够合并一步完成,前面提到的 WebAssembly.instantiate 就作了这两个事情。
WebAssembly.instantiate(bytes).then(mod=>{
mod.instance.f(50); })
7、webAssembly 调用 JS
以前的例子都是用 JS 去调用 WebAssembly 模块,可是在有些场景下可能须要在 WebAssembly 模块中调用浏览器 API,接下来介绍如何在 WebAssembly 中调用 JS。
WebAssembly.instantiate 函数支持第二个参数 WebAssembly.instantiate(bytes,importObject),这个 importObject 参数的做用就是 JS 向 WebAssembly 传入 WebAssembly 中须要调用 JS 的 JS 模块。举个具体的例子,改造前面的计算斐波那契序列在 WebAssembly 中调用 Web 中的 window.alert 函数把计算结果弹出来,为此须要改造加载 WebAssembly 模块的 JS 代码:
WebAssembly.instantiate(bytes,{
window:{ alert:window.alert } }).then(mod=>{ mod.instance.f(50); })
对应的还须要修改 AssemblyScript 编写的源码:
// 声明从外部导入的模块类型 declare namespace window { export function alert(v: number): void; } function _f(x: number): number { if (x == 1 || x == 2) { return 1; } return _f(x - 1) + _f(x - 2) } export function f(x: number): void { // 直接调用 JS 模块 window.alert(_f(x)); }
修改以上 AssemblyScript 源码后从新用 asc 经过命令 asc f.ts 编译后输出的 wast 文件比以前多了几行:
(import "window" "alert" (func $src/asm/module/window.alert (type 0))) (func $src/asm/module/f (type 0) (param f64) get_local 0 call $src/asm/module/_f call $src/asm/module/window.alert)
多出的这部分 wast 代码就是在 AssemblyScript 中调用 JS 中传入的模块的逻辑。
除了以上经常使用的 API 外,WebAssembly 还提供一些 API,你能够经过这个 d.ts 文件去查看全部 WebAssembly JS API 的细节。
8、不止于浏览器
webAssembly做为一种底层字节码,除了能在浏览器中运行外,还能在其余环境下运行。
一、直接执行wasm二进制文件
前面提到的 Binaryen 提供了在命令行中直接执行 wasm 二进制文件的工具,在 Mac 系统下经过 brew install binaryen 安装成功后,经过 wasm-shell f.wasm 文件便可直接运行
二、在nodejs中运行
目前 V8 JS 引擎已经添加了对 WebAssembly 的支持,Chrome 和 Node.js 都采用了 V8 做为引擎,所以 WebAssembly 也能够运行在 Node.js 环境中;
V8 JS 引擎在运行 WebAssembly 时,WebAssembly 和 JS 是在同一个虚拟机中执行,而不是 WebAssembly 在一个单独的虚拟机中运行,这样方便实现 JS 和 WebAssembly 之间的相互调用。
要让上面的例子在 Node.js 中运行,可使用如下代码:
const fs = require('fs'); function toUint8Array(buf) { var u = new Uint8Array(buf.length); for (var i = 0; i < buf.length; ++i) { u[i] = buf[i]; } return u; } function loadWebAssembly(filename, imports) { // 读取 wasm 文件,并转换成 byte 数组 const buffer = toUint8Array(fs.readFileSync(filename)); // 编译 wasm 字节码到机器码 return WebAssembly.compile(buffer) .then(module => { // 实例化模块 return new WebAssembly.Instance(module, imports) }) } loadWebAssembly('../temp/assembly/module.wasm') .then(instance => { // 调用 f 函数计算 console.log(instance.exports.f(10)) });
在 Nodejs 环境中运行 WebAssembly 的意义其实不大,缘由在于 Nodejs 支持运行原生模块,而原生模块的性能比 WebAssembly 要好。 若是你是经过 C、Rust 去编写 WebAssembly,你能够直接编译成 Nodejs 能够调用的原生模块。
9、webAssembly展望
从上面的内容可见 WebAssembly 主要是为了解决 JS 的性能瓶颈,也就是说 WebAssembly 适合用于须要大量计算的场景,例如:
(1)在浏览器中处理音视频,flv.js 用 WebAssembly 重写后性能会有很大提高;
(2)react的dom diff 中涉及到大量计算,用webAssembly重写react核心模块能提高性能。safari浏览器使用的JS引擎JavaScriptCore也已经支持webAssembly,RN应用性能也能提高。
(3)突破大型3D网页游戏性能瓶颈,白鹭引擎已经开始探索用 WebAssembly。
10、总结
WebAssembly 标准虽然已经定稿而且获得主流浏览器的实现,但目前还存在如下问题:
(1)浏览器兼容性很差,只有最新版本的浏览器支持,而且不一样浏览器对JS webAssembly互相调的API支持不一致。
(2)生态工具不完善不成熟,目前还不能找到一门体验流畅的编写webAssembly的语言,都还处于初步阶段。
(3)学习资料太少,还须要更多的人去探索去采坑。
总之如今的 WebAssembly 还不算成熟,若是你的团队没有不可容忍的性能问题,那如今使用 WebAssembly 到产品中还不是时候, 由于这可能会影响到团队的开发效率,或者遇到没法轻易解决的坑而阻塞开发。
11、参考
一、http://www.ruanyifeng.com/blog/2017/09/asmjs_emscripten.html
二、https://www.ibm.com/developerworks/cn/opensource/os-cn-clang/index.html
三、https://developer.mozilla.org/zh-CN/docs/WebAssembly/Understanding_the_text_format
四、https://developer.mozilla.org/zh-CN/docs/WebAssembly/Using_the_JavaScript_API