使用Go開發前端應用

前言

咱們知道,在目前各類容器化盛行的時代,Go在開發容器化應用當中,成爲你們首選的後端開發語言。目前,最流弊的容器化管理編排系統k8s,幾乎每一個大的雲廠商都在使用。而k8s就是Google使用go語言開發出來的。而如今,go已經能夠用來開發前端語言了,有種「一切能夠用go語言實現的功能,最終都會用go語言實現」的感受。這篇文章主要用來介紹,用go語言如何入門前端開發。javascript

go開發環境安裝

首先,你須要先下載安裝一下go。下載地址:golang.org/ 安裝其實很簡單,這裏就不說了,安裝完成以後,控制檯執行下以下命令,確認下go的安裝是否成功。html

go version
複製代碼

若是可以正常輸出,證實你的環境已經安裝好了,是否是很簡單?前端

go爲何能夠用於前端開發

go在1.11版本中,加入了對 WebAssembly 的體驗支持,目前go的版本已經到了1.14了,能夠說對於 WebAssembly 已經支持的很是好了。關於Go語言中 WebAssembly 的更多信息,能夠查看官方的wiki: github.com/golang/go/w…java

正由於go編寫的代碼能夠轉化爲WebAssembly,而WebAssembly又是能夠在任何現代瀏覽器中運行的二進制格式的語言,因此,使用Go來開發前端應用,也就成爲了可能。linux

一個簡單的demo入門

直接看代碼: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…

參考資料:

developer.mozilla.org/zh-CN/docs/…

dev.to/talentlessg…

github.com/golang/go/w…

golang.org/pkg/syscall…

相關文章
相關標籤/搜索