咱們知道,在目前各類容器化盛行的時代,Go在開發容器化應用當中,成爲你們首選的後端開發語言。目前,最流弊的容器化管理編排系統k8s,幾乎每一個大的雲廠商都在使用。而k8s就是Google使用go語言開發出來的。而如今,go已經能夠用來開發前端語言了,有種「一切能夠用go語言實現的功能,最終都會用go語言實現」的感受。這篇文章主要用來介紹,用go語言如何入門前端開發。javascript
首先,你須要先下載安裝一下go。下載地址:golang.org/ 安裝其實很簡單,這裏就不說了,安裝完成以後,控制檯執行下以下命令,確認下go的安裝是否成功。html
go version
複製代碼
若是可以正常輸出,證實你的環境已經安裝好了,是否是很簡單?前端
go在1.11版本中,加入了對 WebAssembly 的體驗支持,目前go的版本已經到了1.14了,能夠說對於 WebAssembly 已經支持的很是好了。關於Go語言中 WebAssembly 的更多信息,能夠查看官方的wiki: github.com/golang/go/w… 。java
正由於go編寫的代碼能夠轉化爲WebAssembly,而WebAssembly又是能夠在任何現代瀏覽器中運行的二進制格式的語言,因此,使用Go來開發前端應用,也就成爲了可能。linux
直接看代碼:git
好比你的html頁面的代碼以下:github
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="test">test</button>
</body>
</html>
複製代碼
頁面當中,有一個button元素,button的id爲「test」。golang
下面來看下在Go語言中怎麼獲取這個元素。web
package main
import (
"syscall/js"
)
js.Global().Get("document").Call("getElementById", "test")
複製代碼
在上面的代碼中,咱們調用「syscall/js」包裏面,提供的方法,來獲取document對象,而且調用document的getElementById方法來獲取咱們html頁面中的button元素。可是到這裏,其實什麼都看不出來,咱們嘗試獲取完button元素以後,將button的按鈕文字修改成「changed by go」。windows
btn := js.Global().Get("document").Call("getElementById", "test")
btn.Set("innerHTML", "changed by go")
複製代碼
寫完以後,代碼大概是上面這個樣子,其餘部分就不貼了。到這裏,一個基本的demo算寫完了,那怎麼來測試呢?
首先咱們須要將go的代碼,編譯成 WebAssembly,而後咱們還須要用到go給咱們提供的一個js庫,這個是用來在js中,調用go編譯生成的WebAssembly,而後執行裏面的代碼邏輯用的。
首先咱們複製下go提供的js庫到目錄中。
在項目根目錄下運行下面的命令:
cp $(go env GOROOT)/misc/wasm/wasm_exec.js .
複製代碼
運行完以後,大概是這個樣子。
而後咱們須要編譯go代碼成wasm格式。
使用下面的命令,將go代碼編譯成wasm格式。
GOOS=js GOARCH=wasm go build -o main.wasm main.go
複製代碼
這裏須要說明一下,GOOS和GOARCH這兩個環境變量的做用。 在go裏面,能夠將go代碼編譯成各個平臺的目標結果。好比GOOS,能夠指定爲windows或者linux等。在這裏,還能夠指定爲js。
GOARCH表示系統架構,好比能夠指定爲amd64或者386等。在這裏,還能夠指定爲wasm。
執行上面的命令以後,咱們能夠看到目錄下多了一個wasm的文件。
到這裏,準備工做差很少了。咱們須要在html中引入go提供的js庫,而後去使用剛剛咱們編譯生成的main.wasm了。
修改html,以下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="test"></button>
</body>
<script src="./wasm_exec.js"></script>
<script> const go = new Go() WebAssembly.instantiateStreaming(fetch('app.wasm'), go.importObject) .then(result => go.run(result.instance)) </script>
</html>
複製代碼
上面的代碼,WebAssembly.instantiateStreaming方法直接從流式底層源編譯和實例化WebAssembly模塊。這是加載wasm代碼一種很是有效的優化方式。
fetch就不用說了。
go.importObject
是一個對象,這個對象會被導入到 wasm的模塊中,這樣在wasm的模塊中就能夠訪問到js對象。
在這裏,go.importObject大概長這樣子:
看go提供的js庫中的源碼,裏面有註釋。
這裏的importObject主要是用來在wasm文件裏面調用js代碼的(在wasm裏面調用js提供的方法),在go裏面,主要使用來處理SP(Stack Pointer)的變動。
上面的代碼準備好以後,咱們能夠啓動一個http的服務,推薦使用http-server來啓動, github.com/http-party/…
啓動完成以後,訪問 http://127.0.0.1:8080/
能夠看到,訪問以後,正確還在了咱們的wasm文件,而且執行了咱們以前用go寫的代碼,將button的文字改爲了「changed by go」。
上面的代碼,咱們只是在訪問的時候,修改了按鈕的文字,並無別的任何操做,下面來看下若是,給按鈕添加一個點擊事件。
首先咱們須要聲明一個函數,用來做爲點擊事件的回調函數。
func main() {
btn := js.Global().Get("document").Call("getElementById", "test")
callback := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
fmt.Println(this)
fmt.Println(args)
fmt.Println("button clicked")
return nil
})
btn.Set("innerHTML", "changed by go")
btn.Call("addEventListener", "click", callback)
}
複製代碼
上面的代碼中,首先,經過調用js包的FuncOf建立了一個用於在js裏面調用的函數,在FuncOf的參數裏面,咱們能夠看到定義的回調函數,這個函數有兩個參數,第一個參數表明你js調用的時候的this對象,第二個參數表示調用時候的參數。
添加完上面的代碼以後,咱們從新生成下wasm文件,而後刷新頁面,點擊下按鈕,看下是否會輸出「button clicked」這個字符串。
點擊完成以後,發現報錯了,提示go程序已經退出,這是爲啥呢?
看上面的代碼,咱們發如今main函數裏面,執行完全部的代碼以後,go的主線程就直接退出了,而咱們使用js.FuncOf建立的回調函數,實際上是在單獨的一個goroutine裏面執行的,主線程都退出了,那goroutine天然沒法執行了。
爲了解決這個錯誤,咱們須要保證主線程不退出。 修改代碼以下:
func main() {
btn := js.Global().Get("document").Call("getElementById", "test")
signal := make(chan int)
var callback js.Func
callback = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
fmt.Println(this)
fmt.Println(args)
fmt.Println("button clicked")
return nil
})
btn.Set("innerHTML", "changed by go")
btn.Call("addEventListener", "click", callback)
<- signal
}
複製代碼
這裏加了一個channel類型的變量,關於channel的知識,能夠查看官方的文檔,或者看我以前寫的go學習筆記(juejin.im/post/5a2b4e…
這裏使用channel主要用來防止go的主線程退出,在最後一句,<- signal , 表示從這個signal的通道中獲取數據,可是咱們能夠看到,並無地方給這個通道塞入數據,因此,主線程會一直阻塞在這裏,這樣,咱們的事件回調纔會正常執行。
看下正常執行的結果:
能夠發現,咱們給button註冊的點擊事件,能夠正常觸發,而且回調函數也正常執行了。
若是仔細看上面的代碼,發現使用Go來操做dom的話,仍是比較麻煩的, 好比每次獲取一個dom元素都須要:
js.Global().Get("document").Call("getElementById", "test")
複製代碼
還有,咱們只能這樣調用dom的方法:
btn.Call("addEventListener", "click", callback)
複製代碼
這裏方法名稱做爲了參數,很容易失誤寫錯。
全部,社區就有人將這些操做給封裝了起來,好比: godoc.org/honnef.co/g…
這個庫。
查看文檔,這個時候發現跟咱們平時使用js操做dom的寫法就比較一致了。
Go近些年在國內愈來愈流行了,特別是上雲,容器化發展以後。關鍵的是,Go不只性能好,並且佔用內存等也很是少,目前大部分新的後臺項目也都在使用Go重寫。
說明:
由於有評論說到適用的問題,這裏說明一下,首先,普通的前端應用徹底沒有必要適用Go來開發wasm,由於可能你的項目場景就不須要用到wasm,那強行用的話,除了複雜度增長沒有帶來什麼好處。可是在一些特殊場景下,須要使用wasm的時候,這個時候,你是用Go來開發,就比較爽了。 wasm的使用場景能夠參考:
webassembly.org.cn/docs/use-ca…
blog.logrocket.com/webassembly…
參考資料: