1.前言
參加完2018年上海的QCon大會,想到了會議中來自Microsoft的朱力旻大佬講的WebAssembly,感觸頗深。
我之前完全沒有了解過WebAssembly,之前沒有了解的原因也很簡單,沒有什么實際的應用場景,但工欲善其事,必先利其器。
抱着這樣的想法,便開始入坑WebAssembly。
2.Why WebAssembly
2.1 javascript的背景
JavaScript這門由Brendan Eich花了10天創造出來的語言,在如今收到了廣泛的應用,同時也因為其缺陷遭受到了業界的一些詬病。的確,這門被倉促的創造出來的語言有很多缺陷。畢竟JavaScript結合了函數式編程和面向對象編程,在JavaScript之前的參考極少。
並且JavaScript發展過於迅速,沒有多少時間去調整標准。比如JavaScript的變量是一個動態變量。上一秒這個變量可以是Array,下一秒就變成了一個Number。
JavaScript的語法太過於靈活,這樣導致JavaScript在開發大型應用上成為了瓶頸,並且類似於CAD這種網格模型對於性能要求特別高的情況下,JavaScript根本無法勝任。
2.2 社區的補救
例如,微軟給出了TypeScript這個解決方案,TypeScript為JavaScript添加了靜態類型檢查,提升了代碼的健壯性。但是最終TypeScript還是要編譯成JavaScript來運行。
於是在2013年,為了推動Web性能的發展。WebAssembly的前身asm.js誕生了。下面給個例子。
function asmJS() {
'use asm';
let myInt = 0 | 0;
let myDouble = +1.1;
}
傳統的JavaScript的動態變量需要在代碼執行的時候,編譯器才知道當前變量的類型。我們都知道"use strict",這是JavaScript的嚴格模式。
通過添加代碼 "use strict"來使用嚴格模式。在嚴格模式下的JavaScript代碼會通過拋出錯誤的方式來代替原來的靜默錯誤。並且在某些情況下, 嚴格模式下的代碼運行效率會高於"sloppy mode"下的代碼。
而上述代碼中的"use asm",就是告訴引擎,下面的代碼是asm.js的代碼。當引擎看到"0 | 0",就會在這行代碼運行之前知道,這是一個整形的數據,而不會再去編譯一次。而看到"+1.1"就會知道這是一個浮點型的數據。asm.js是JavaScript的一個子集。
並且將JavaScript的動態變量變成了靜態變量,代碼在運行時,有接近原生的性能。
可能到這里疑問就來了,既然asm.js在運行時已經有了接近native的性能。為什么還會出現WebAssembly這個技術。那是asm.js並不能解決所有的問題。
2.3 asm.js並不能解決所有的問題
為什么說asm.js不能解決所有的問題?拿Microsoft Edge的Javascript引擎舉個例子。

上面這張圖是ChakraCore引擎的結構。Chakra是Microsoft Edge瀏覽器開源的Chakra Javascript引擎的核心部分。我們在瀏覽器中運行的Javascript代碼首先會經過編譯,解析成AST(AKA抽象語法樹),想實操的可以去這里試試。
asm.js及時讓其運行性能接近了native,但是仍然需要經過parser這一步。而這一步是整個過程中最費時的一步。這就給asm.js造成了一個瓶頸。
2.4 WebAssembly橫空出世
於是2015年,WebAssembly出現了。WebAssembly是一個可移植、體積小、加載快並且即兼容Web的匯編格式。
與asm.js類似的是,WebAssembly擁有靜態類型。同時也是編譯目標,並且可移植。而不同於asm.js,WebAssembly是匯編格式,代碼量小,起步相對較快。而且語法上完全脫離JavaScript。這是WebAssembly官方給出的一個demo,可以去體驗一下。
3 WebAssembly快速入門
WebAssembly為什么說是編譯的目標。是因為WebAssembly可以將C和C++編譯成WebAssembly,用的工具是Emscripten,是一個預編譯工具。要進行WebAssembly的編譯,需要安裝git、cmake、python
假設你是在mac上進行操作的。
3.1 安裝
安裝WebAssembly工具鏈。
git clone https://github.com/juj/emsdk.git
cd emsdk
./emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit
./emsdk activate --build=Release sdk-incoming-64bit binaryen-master-64bit
可能會等的有點久,並且可能會出現cmake沒有安裝的報錯信息。可以使用homebrew安裝cmake后,再執行安裝操作。
3.2 設置環境變量
在emsdk目錄下,運行下列命令。就可以在當前的終端窗口中運行emcc命令了。
source ./emsdk_env.sh --build=Release
3.3 編寫C或者C++文件
新建一個C文件,代碼如下。
#include <stdio.h>
long factorial(int num) {
if (num <= 0) return 1;
else {
return num * factorial(num - 1);
}
}
int main () {
int num = factorial(10);
printf("The Result: %d \n", num);
}
3.4 利用Emscripten編譯代碼
然后在相同目錄下執行emcc的編譯命令,該命令會將C或者C++文件編譯成wasm模塊。
emcc hello.c -s WASM=1 -o hello.html
其中,hello.c為源文件,-o hello.html是這個命令的輸出文件,-s WASM指定輸出的格式為wasm,並且版本為1。執行這個文件后,會在目錄下生成如下數據。
.
├── hello.c
├── hello.html
├── hello.js
└── hello.wasm
hello.wasm就是經過編譯后的C代碼的二進制文件。而hello.js則是C語言與JavaScript相互轉化的中間代碼。執行下面的代碼就可以在localhost的8080端口看到我們的C模塊在前端的調用和展示。
emrun --no_browser --port 8080 .
然后就可以在瀏覽器中看到如下的輸出。

我們編寫的C模塊已經可以在瀏覽器中正常的運行了。這說明我們的WebAssembly編譯成功了。
4 在前端項目中使用Wasm
可以參考這篇文章,如何在React項目中直接使用WebAssembly,在這篇文章的項目中可以看到完整的從wasm文件中提取出函數的function,以及清晰的用C實現的函數和用JavaScript實現的函數的運行性能對比。
