不安分的 Go 語言開始入侵 Web 前端領域了

從 Go 語言誕生以來,它就開始不斷侵蝕 Java 、C、C++ 語言的領地。今年下半年 Go 語言發佈了 1.11 版本,引入了 WebAssembly 技術,瀏覽器端 Javascript 的壟斷地位也開始遭遇 Go 語言的攻擊。此次不一樣以往,它意味着 Go 語言從後端滲透進了前端,進入了一個全新的世界。javascript

WebAssembly 運行原理

WebAssembly 這個名字翻譯過來就是 「Web 彙編」,也就是 Web 端的彙編語言。它是一段二進制字節碼程序,Javascript 能夠將這段二進制程序編譯成模塊,而後再實例化這個模塊就能夠調用字節碼邏輯了。WebAssembly 代碼運行的速度很快,比 Javascript 要快不少,Javascript 能夠經過 WebAssembly 技術將關鍵性耗費性能的邏輯交給 WebAssembly 來作就能夠明顯提高瀏覽器端的性能。html

對比顯示,使用 WebAssembly 運行斐波那契數列相比使用原生 Javascript 來實現,運行效率上能帶來 3.5 倍的提高。前端

WebAssembly 是一項比較新的技術,只有比較現代的瀏覽器才支持 WebAssembly,例如 Chrome、FireFox瀏覽器。java

Go WebAssembly 運行原理

Go 編譯器能夠將代碼編譯成 WebAssembly 二進制字節碼,被瀏覽器以靜態資源的形式加載進來後轉換成 Javascript 模塊。有了這個模塊,瀏覽器能夠直接操縱 Go 語言生成的二進制字節碼邏輯。同時在 Go 語言編寫的代碼中能夠直接讀寫瀏覽器裏面 Javascript 運行時對象,這樣就完成了 Javascript 和 Go 代碼的雙向交互。git

Go 語言直到 1.11 版本以後纔開啓了對 WebAssembly 的支持。如需體驗,必須升級。github

Go WebAssembly 初體驗

下面咱們就開始體驗一下 Chrome 瀏覽器與 Go 代碼是如何交互的。咱們要實現一個功能,在瀏覽器的輸入框裏輸入一個正整數,而後調用 Go 代碼的斐波那契數列,再將結果再呈如今頁面上。涉及到 4 個文件,分別是 fib.go、main.go、index.html、wasm_exec.js。後端

第一步

使用 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 函數體就銷燬了。bash

第二步

下面將 Go 代碼編譯成 WebAssembly 二進制字節碼。前端框架

$ GOARCH=wasm 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">
	<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))
}
複製代碼

使用下面的命令運行它

$ go run main.go
複製代碼

第五步

打開瀏覽器,訪問 http://localhost:8000,如今就能夠體驗它的運行效果了。

Javascript 真的須要擔憂 Go WebAssembly 的威脅麼?

其實根本不用擔憂,WebAssembly 的目的是替換前端運行比較耗時的邏輯,不是用來替換前端框架的,它也替換不了。雖然開源社區冒出了一個 github.com/elliotforbe… 的 Go WebAssembly 框架,可讓你使用 Go 語言編寫前端應用程序。可是我仔細看了一下它的的源碼,發現它原來只是一個玩具 ^_^,實現上沒幾行代碼,離真實的應用程序差距太遠。

若是 Go WebAssembly 對 javascript 是個威脅,那麼威脅 javascript 的可不止 Go 語言了,可以將代碼編譯成 WebAssembly 字節碼的語言多達幾十種。

但願將當前 javascript 項目的部分代碼替換成 Go 語言,成本也是顯而易見的。技術棧的切換成本,字節碼的加載成本,框架項目持續集成的成本都是須要考慮的點。除非能得到巨大的性能提高,不然使用純粹的 javascript 來完成項目依然是最佳選擇。

微信搜索「codehole」,關注公衆號「碼洞」

相關文章
相關標籤/搜索