不安分的 Go 語言開始入侵 Web 前端領域了!( WebAssembly )
參考:https://blog.csdn.net/csdnnews/article/details/84038848
從 Go 語言誕生以來,它就開始不斷侵蝕 Java 、C、C++ 語言的領地。今年下半年 Go 語言發布了 1.11 版本,引入了 WebAssembly 技術,瀏覽器端 Javascript 的壟斷地位也開始遭遇 Go 語言的攻擊。這次不同以往,它意味著 Go 語言從後端滲透進了前端,進入了一個全新的世界。
WebAssembly 是一項比較新的技術,只有比較現代的瀏覽器才支持 WebAssembly,例如 Chrome、FireFox瀏覽器。
Go 編譯器可以將代碼編譯成 WebAssembly 二進制字節碼,被瀏覽器以靜態資源的形式加載進來後轉換成 Javascript 模塊。有了這個模塊,瀏覽器可以直接操縱 Go 語言生成的二進制字節碼邏輯。同時在 Go 語言編寫的代碼中可以直接讀寫瀏覽器裏面 Javascript 運行時對象,這樣就完成了 Javascript 和 Go 代碼的雙向交互。
Go 語言直到 1.11 版本之後才開啟了對 WebAssembly 的支持。如需體驗,必須升級。
第一步
使用 Go 代碼編寫 WebAssembly 模塊文件 fib.go,將 Go 語言實現的斐波那契函數註冊到 Javascript 全局環境。這需要使用內置的 syscall/js 模塊,它提供了和 Javascript 引擎交互的接口。
// fib.go package main import "syscall/js" func main() { f_fib := func(params []js.Value) { var n = params[0].Int() //輸入參數 var callback = params[1] // 回調參數 var result = fib(n) // 調用回調函數,傳入計算結果 callback.Invoke(result) } // 註冊全局函數 js.Global().Set("fib", js.NewCallback(f_fib)) // 保持 main 函數持續運行 select {} } // 計算斐波那契數 func fib(n int) int { if n <= 0 { return 0 }var result = make([]int, n+1) result[0] = 0 result[1] = 1 if n <= 1 { return result[n] } for i := 2; i <= n; i++ { result[i] = result[i-2] + result[i-1] } return result[n] }
Go 語言註冊到 Javascript 引擎的函數在執行時是異步的,所以這個函數沒有返回值,在完成計算後需要通過調用「傳進來的回調函數」將結果傳遞到 Javascript 引擎。註意 main 函數要保持運行狀態不要退出,不然註冊進去的 fib 函數體就銷毀了。
第二步
下面將 Go 代碼編譯成 WebAssembly 二進制字節碼。(我的是windows)
set GOARCH=wasmset GOOS=js
go build -o fib.wasm fib.go
執行完成後可以看到目錄下多了一個 fib.wasm,這個就是字節碼文件。它的大小是 1.3M,作為靜態文件傳遞到瀏覽器似乎有點大,不過靜態文件服務器一般有 gzip 壓縮,壓縮後的大小只有幾百K,這差不多也可以接受了。
第三步
編寫網頁文件 index.html,這個網頁包含兩個輸入框,第一個輸入框用來輸入整數參數,第二個輸入框用來呈現計算結果。當第一個輸入框內容發生改變時,調用 Javascript 代碼,執行通過 WebAssembly 註冊的 fib 函數。需要傳入參數 n 和回調的函數。
<html> <head> <meta charset="utf-8"> <meta http-equiv="expires" content="0"> <title>Go wasm</title> </head> <style> body { text-align: center } input { height: 50px; font-size: 20px; } #result { margin-left: 20px; } </style> <body> <script src="wasm_exec.js"></script> <script> // 容納 WebAssembly 模塊的容器 var go = new Go(); // 下載 WebAssembly 模塊並執行模塊 // 也就是運行 Go 代碼裏面的 main 函數 // 這樣 fib 函數就註冊進了 Javascript 全局環境 WebAssembly.instantiateStreaming(fetch("fib.wasm"), go.importObject).then((result) => { go.run(result.instance); }); function callFib() { let paramInput = document.getElementById("param") let n = parseInt(paramInput.value || "0") // 傳入輸入參數和回調函數 // 回調函數負責呈現結果 fib(n, function(result) { var resultDom = document.getElementById("result") resultDom.value = result }) } </script> // 輸入發生變化時,調用 WebAssembly 的 fib 函數 <input type="number" id="param" oninput="callFib()"/> <input type="text" id="result" /> </body> </html>
註意代碼中引入了一個特殊的 JS 文件 wasm_exec.js,這個文件可以從 Go 安裝目錄的 misc 子目錄裏找到,將它直接拷貝過來。它實現了和 WebAssembly 模塊交互的功能。
第四步
運行靜態文件服務器,這裏不能使用普通的靜態文件服務器,因為瀏覽器要求請求到的 WebAssemly 字節碼文件的 Content-Type 必須是 application/wasm,很多靜態文件服務器並不會因為擴展名是 wasm 就會自動使用這個 Content-Type。但是 Go 內置的 HTTP 服務器可以。所以下面我們使用 Go 代碼簡單編寫一個靜態文件服務器。
package main import ( "log" "net/http" ) func main() { mux := http.NewServeMux() mux.Handle("/", http.FileServer(http.Dir("."))) log.Fatal(http.ListenAndServe(":8000", mux)) }
現在在項目目錄下有四個文件:main.go、index.html、fib.wasm、wasm_exec.js
用以下命令編譯運行:
go run main.go
第五步
打開瀏覽器,訪問 http://localhost:8000,現在就可以體驗它的運行效果了。
Javascript 真的需要擔心 Go WebAssembly 的威脅麽?
其實根本不用擔心,WebAssembly 的目的是替換前端運行比較耗時的邏輯,不是用來替換前端框架的,它也替換不了。雖然開源社區冒出了一個 https://github.com/elliotforbes/oak 的 Go WebAssembly 框架,可以讓你使用 Go 語言編寫前端應用程序。但是我仔細看了一下它的的源碼,發現它原來只是一個玩具,實現上沒幾行代碼,離真實的應用程序差距太遠。
如果 Go WebAssembly 對 Javascript 是個威脅,那麽威脅 Javascript 的可不止 Go 語言了,能夠將代碼編譯成 WebAssembly 字節碼的語言多達幾十種。
希望將當前 Javascript 項目的部分代碼替換成 Go 語言,成本也是顯而易見的。技術棧的切換成本,字節碼的加載成本,框架項目持續集成的成本都是需要考慮的點。除非能獲得巨大的性能提升,否則使用純粹的 Javascript 來完成項目依然是最佳選擇。
原作者簡介:老錢,著有《Redis 深度歷險》《深入理解 RPC》《快學 Go 語言》。
熟練使用 Java、Python、Golang 等多種計算機語言,開發過遊戲,制作過網站,寫過消息推送系統和 MySQL 中間件,實現過開源的 ORM 框架、Web 框架、RPC 框架等,目前任職掌閱服務端技術專家。
不安分的 Go 語言開始入侵 Web 前端領域了!( WebAssembly )