使用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…

相关文章
相关标签/搜索