WebAssembly 實戰 – 讓 Go 與 JS 在瀏覽器上共舞
如果你有在關注前端技術的話應該多少有聽過 WebAssembly(Wasm) 這東西,WebAssembly 是一種跑在瀏覽器上的低階語言,他在瀏覽器上能 以接近原生應用的效能執行,所以以前受限於效能而比較難做到的那些應用(高畫質遊戲、繪圖軟體等等),以後很有可能在網頁上就可以做到,光想到就覺得好興奮啊啊啊
雖然 WebAssembly 聽起來很厲害,但他的語法可不是普通的難寫
下圖是我用 WebAssembly 寫了個 fib
函數來求費氏數列的某一項。其他語言的 n-1
在 Wasm 裡面必須寫成 (i32.sub (get_local $n) (i32.const 1))
才行,真的很有組合語言的 fu 呢 😂,原本兩三行就能解決的 fib
現在非得寫成好幾行不可,光想到頭就痛了
慶幸的是現在很多語言(C++/Go/Rust)都能透過 Emscripten 編譯成 WebAssembly,所以其實你不需要真的寫 WebAssembly,只需要把你寫完的 C++/Go/Rust 程式碼編譯成 Wasm 就好
如果從前端技術發展的角度來看:只要瀏覽器可以執行 Wasm,那各語言的開發者就能用他們熟悉的語言開發功能,然後再編譯成 Wasm 就能在瀏覽器上執行。也就是說
瀏覽器終於不是只能跑 JavaScript,而是可以跑任何能編譯成 Wasm 的語言 🎉🎉🎉
而這篇文章就是要實際用 Go 完成一些功能、編譯成 Wasm 在網頁上執行,並且比較 Wasm 跟原生 JS 的 效能
雖然說會用到 Go,但如果你對他不熟悉也沒關係,因為 Go 這個語言還滿好學的,稍微看一下官網的 A Tour of Go 就可以上工了~
瀏覽器支援
雖然 WebAssembly 這東西聽起來很高科技XD,但其實主流的瀏覽器都已經支援了哦~除非你的網站需要支援 IE 或是比較舊款的手機,不然基本上不會有問題
目前也已經有人用 WebAssembly 做出一些應用,像下面這個專案是把 Vim 編譯成 Wasm,並且讓他跑在瀏覽器上
從 Devtool 也可以看到他確實是用 WebAssembly 實作的哦(黃框處 application/wasm
)
上面說了這麼多都是在講 WebAssembly 技術的近況,接下來終於要開始寫扣了(摩拳擦掌
事前準備
等等會用到 Go 提供的 wasm_exec.js
作為 Wasm 跟 JS 之間溝通的橋樑,另外還需要啟動一個 Web Server(我是用 simplehttpserver),先把他們準備好
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
npm install -g simplehttpserver
Hello World in Go
學任何語言都要從 Hello World 開始學起,用 Wasm 當然也是,先用 Go 寫一個簡單的 Hello World
編譯時加上 GOOS=js
跟 GOARCH=wasm
,這樣 Go 就會編譯出一個 main.wasm
GOOS=js GOARCH=wasm go build -o main.wasm
為了讓瀏覽器載入編譯完的 Wasm 檔案,再寫一個 index.html
來抓 main.wasm
之後下 simplehttpserver .
把 Web Server 跑起來就能在瀏覽器 Console 看到可愛的 Hello World,而且這個 Hello World 是用 Go 寫的哦,很神奇吧~
syscall/js
上面已經成功跑出 Go 寫的 Hello World,很多文章也都只有講到這裡。但我猜會用 WebAssembly 大部分都是為了解決效能的問題,所以來說說要怎麼用 Go 寫一些高效能的 function 給 JS 用
為了達到這個目的,Go 有提供一個 syscall/js package 在處理 Go 跟 JS 之間的型別轉換、函數綁定:譬如說把 int
對應到 Number
、還有讓 JS 能夠使用 Go 寫的函數,如此一來就能把需要效能的地方用 Go 寫
下面的例子是用 Go 實作一個 add
負責把 args[0]
跟 args[1]
相加,並且把 add
放到全域給 JS 用
因為 add
function 已經被編譯成 WebAssembly 並放到全域,所以在 Console 裡面可以直接用
還是要再強調一次,這個
add
是用 Go 寫的哦,超酷的~
既然用 JS 跟 Go(WebAssembly) 都可以完成同樣的功能,那他們的效能比起來怎麼樣?
我分別用 Go 跟 JS,實作了同樣的演算法(Merge Sort)來排序一百萬個整數,大家猜猜 Wasm 的效能會快多少?是二倍五倍還是甚至十倍呢?
其實測出來竟然沒快多少XDD,原本還以為會快個三五倍 😂,但不管是在 Chrome 還是 Firefox 上測,WebAssembly 大概都只快了 10% 左右,看來 Wasm 也沒想像中那麼厲害嘛
好啦認真說,我猜可能是因為 WebAssembly 的編譯、執行技術才剛發展不久,還有不少待改進的地方;而 JS 因為歷史悠久、瀏覽器早已對他做了各種最佳化,所以才會得到這樣的結果,相信以後 WebAssembly 還會越來越快
DOM Manipulation
除了把 Go 寫的 function 給 JS 用之外,其實 Go 也能透過 syscall/js 提供的 API 來取得 JS 全域變數、操作 DOM
以下面實作的 hello
來說,我先用 document.createElement
建立一個 h1 Element,設定好內容 Hello World 後再把他塞到 document.body
裡面去
這時在瀏覽器裡面執行 hello()
就會看到新增的 Hello World 了~
其實 WebAssembly 本身是不能操作 DOM 的,是因為
exec_wasm.js
把這些 DOM 操作封裝成 WASM function 了才能這樣用
雖然這例子證明了 Go 可以操作 DOM,代表你可以 只用 Go 就完成整個前端應用,但我自己是覺得滿彆扭的啦,好好的 document.body
非得寫成 js.Global().Get("document").Get("body")
不可。如果只是寫個小範例還可以,要我用這種方式做一個專案我應該會先氣死
反思 Atwood’s Law
不知道各位有沒有聽過著名的 Atwood 定律:任何可以用 JavaScript 寫出來的應用,最終都會用 JavaScript 來寫
Any application that can be written in JavaScript, will eventually be written in JavaScript.
但在 WebAssembly 的快速發展之下,其他語言也紛紛開始搶食瀏覽器這塊大餅,就連跟前端八竿子打不著的 Go 都能開始寫前端了,以後 JS 還能不能維持現在的霸主地位好像也很難說
總結
回歸到這篇的標題 — 「讓 Go 與 JS 在瀏覽器上共舞」,看完這幾個小例子想必大家都對 Go 跟 WebAssembly 有更多認識,應該也知道 Go 跟 JS 要怎麼在瀏覽器上合作了。怕大家忘記再重點整理一下今天的內容:
- 主流瀏覽器都有支援 WebAssembly
- Go 可以寫 function 給 JS 用
- Go 編譯成的 WebAssembly 比 JS 稍快一點
- Go 可以藉由 syscall/js 來操作 DOM,但很難用
範例都放在 Github 上,歡迎各位看倌自己 clone 下來跑跑看,對於文章或是範例扣有什麼問題的話也歡迎在下方留言跟我討論,謝謝大家