介绍
在 go 1.11 的时候 go 官方向 go 实验性的添加了 WebAssembly 支持,也就是说 golang 可以编译 为 wasm 以供 JavaScript 进行调用。
在这里我想说一下我的感受,在编写代码时应该是主要编写 go 的代码供 JavaScript 调用 而不是在 在 go 中调用 JavaScript 代码。
目前 go 还只是实验性的支持 wasm 以后可能会变更,我会尽量跟进。
前期准备
这里我使用的是 go1.13 进行演示,ide 使用的是 goland
由于目前只是实验性的支持,在编写代码时 引用 syscall/js
会爆红 需要在ide中将变量环境 指定为 OS=js ARCH=wasm
goland 的设置在 seting -> Go -> Build Tags && Vendoring
中进行更改。
浏览器 我使用的是 chrome 包括 js 部分演示我也是在 chrome 下进行。
首先把创建一个新项目作为本次演示
创建一个新文件夹 nweb
并使用 ide 打开它,接下来的所有演示都将在这里进行。然后开启 go mod,在 nweb
目录内执行 go mod init nweb
由于国内直接拉起依赖可能比较慢,你还可以将 GOPROXY
设置为 https://goproxy.cn
具体请参考 https://github.com/goproxy/goproxy.cn/blob/master/README.zh-CN.md goland 的设置在 seting -> go -> Go Modules(vgo)
。
前期准备
这里主要参考 go wiki
创建 main.go
并写入以下内容
package main
import "fmt"
func main() {
fmt.Println("Hello, WebAssembly!")
}
然后将 main.go
编译为 main.wasm
GOOS=js GOARCH=wasm go build -o main.wasm
创建 index.html
并写入以下内容
<html>
<head>
<meta charset="utf-8"/>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
</script>
</head>
<body></body>
</html>
将 JavaScript 支持文件移动到本目录
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
下载 go 版的 简单 web 服务器
go get -v -u github.com/shurcooL/goexec
然后运行它
goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'
然后打开 chrome 访问 http://127.0.0.1:8080/ 并按下 F12
打开 DevTools 不出意外的话你应该会看到 控制台上输出 Hello, WebAssembly!
接下来就正式开始了,在每次更改 go 代码时 你需要对 go 代码进行重新编译,重启 goexec
并对网页进行刷新。
例子
上面的前期准备是 go wiki 对 wasm 进行的演示接下来我们玩一点不一样的。
go 调用 JavaScript 函数
package main
import "syscall/js"
func main() {
// 执行属性的对用方法 这里执行的方法类似于 // fmt.Println("Hello World!")
js.Global().Get("console").Call("log","Hello World!")
}
重新编译后切换到 chrome 刷新一下 网页 不出意外的话,你的开发者工具中的控制台会输出 Hello World!
JavaScript 调用 go 函数
package main
import (
"fmt"
"syscall/js"
)
func sayHello(this js.Value, args []js.Value) interface{} { //函数的定义格式就是这样 因为这是 js.FuncOf 要求的
fmt.Println("Hello World!") // 执行我们自己的代码
// return nil 这样可能比较符合 go 的习惯
return js.Undefined() // 返回 JavaScript 的 undefined
}
func main() {
js.Global().Set("sayHello", js.FuncOf(sayHello)) // 将 go 函数注册为 JavaScript 函数
c := make(chan struct{}) // 这两句不是多余的 需要阻止程序的退出以使 JavaScript 能正常调用
<-c
}
重新编译后切换到 chrome 刷新一下, 然后在开发者工具(F12)终端中输入 sayHello()
进行调用, 不出意外的话,你的开发者工具中的控制台会输出 Hello World!
将 JavaScript 函数作为参数传递给 go 进行调用
这是很有用的 这样就可以 go 提供处理过后的数据,JavaScript 自己决定数据如何使用
package main
import (
"syscall/js"
)
func autoincrement(this js.Value, args []js.Value) interface{} { //函数的定义格式就是这样 因为这是 js.FuncOf 要求的
fn := args[0] // 获取输入的第一个参数
for i := 0; i < 10;i++{
// 这里我默认输入的参数是 JavaScript 函数 // 非函数调用 Invoke() 会 panic
fn.Invoke(i) // Invoke 的意思是向 JavaScript函数的括号里插入值
}
// return nil 这样可能比较符合 go 的习惯
return js.Undefined() // 返回 JavaScript 的 undefined
}
func main() {
js.Global().Set("autoincrement", js.FuncOf(autoincrement)) // 将 go 函数注册为 JavaScript 函数
c := make(chan struct{}) // 这两句不是多余的 需要阻止程序的退出以使 JavaScript 能正常调用
<-c
}
重新编译并刷新网页
在 chrome 开发者工具控制台 定义一个函数 用来处理我们 go 函数生成的数据
类似于这样
function print(msg){
console.log(msg)
}
然后调用它
autoincrement(print)
最后输出结果类似于这样
q
参看资料
https://github.com/golang/go/wiki/WebAssembly
https://golang.org/pkg/syscall/js/
https://talks.godoc.org/github.com/chai2010/awesome-go-zh/chai2010/chai2010-golang-wasm.slide#1