+
95
-

回答

webassembly是什么?

webassembly是可以支持在web浏览器或者v8等环境下运行的二进制格式文件。

本文只介绍如何使用golang生成wasm文件,并在浏览器上执行。

开始

需要先升级go到1.11版本

编写需要编译成wasm文件的go文件
// main.go
package main

func main() {
println("Hello, WebAssembly!")
}

执行build命令

GOARCH=wasm GOOS=js go build -o test.wasm main.go

注意这个是在mac或者linux操作系统下执行的命令,在windows下应该设置环境变量再执行编译命令

$env:GOARCH="wasm";$env:GOOS="js";

go build -o test.wasm main.go

命令执行完后,后生成test.wasm文件,这个就是可以在浏览器上运行的二进制文件

复制$(go env GOROOT)/misc/wasm/下的wasm_exec.html和wasm_exec.js两个文件到http的服务根目录

在浏览器中打开http://localhost:8080/wasm_exec.html,点击页面中的run按钮,即可看到控制台打印Hello, WebAssembly!

这样我们就已经可以使用go编写一个可以运行在浏览器的程序了

如何使用go的js库syscall/js来实现与js的相互调用

1、js调用go的方法

我们下面通过go生成一个webassembly程序暴露sum函数让js调用,代码如下:

// main.go
package main

import "syscall/js"

func sum(args []js.Value) {
var sum int
for _, val := range args {
sum += val.Int()
}
println(sum)
}
func registerCallbacks() {
js.Global().Set("sum", js.NewCallback(sum))
}
func main() {
c := make(chan struct{}, 0)
println("Hello, WebAssembly!")
registerCallbacks()
<-c
}


重新编译后,刷新页面,点击run按钮,就会为window对象挂载一个sum函数,在控制台可以调用

2、go调用js方法

// main.go
package main

import "syscall/js"

func main() {
alert := js.Global().Get("alert")
alert.Invoke("Hello World!")
}

编译生成main.wasm文件

html

<html>
<script src="static/wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject)
.then((result) => go.run(result.instance));
</script>

</html>


3、操作dom

main.go

package main

import (
"strconv"
"syscall/js"
)

func fib(i int) int {
if i == 0 || i == 1 {
return 1
}
return fib(i-1) + fib(i-2)
}

var (
document = js.Global().Get("document")
numEle = document.Call("getElementById", "num")
ansEle = document.Call("getElementById", "ans")
btnEle = js.Global().Get("btn")
)

func fibFunc(this js.Value, args []js.Value) interface{} {
v := numEle.Get("value")
if num, err := strconv.Atoi(v.String()); err == nil {
ansEle.Set("innerHTML", js.ValueOf(fib(num)))
}
return nil
}

func main() {
done := make(chan int, 0)
btnEle.Call("addEventListener", "click", js.FuncOf(fibFunc))
<-done
}

代码解释:

通过 js.Global().Get("btn") 或 document.Call("getElementById", "num") 两种方式获取到 DOM 元素。
btnEle 调用 addEventListener 为 btn 绑定点击事件 fibFunc。
在 fibFunc 中使用 numEle.Get("value") 获取到 numEle 的值(字符串),转为整型并调用 fib 计算出结果。
ansEle 调用 Set("innerHTML", ...) 渲染计算结果。

html

<html>
<body>
<input id="num" type="number" />
<button id="btn">Click</button>
<p id="ans">1</p>
</body>
</html>

4、回调函数

package main

import (
"syscall/js"
"time"
)

func fib(i int) int {
if i == 0 || i == 1 {
return 1
}
return fib(i-1) + fib(i-2)
}

func fibFunc(this js.Value, args []js.Value) interface{} {
callback := args[len(args)-1]
go func() {
time.Sleep(3 * time.Second)
v := fib(args[0].Int())
callback.Invoke(v)
}()

js.Global().Get("ans").Set("innerHTML", "Waiting 3s...")
return nil
}

func main() {
done := make(chan int, 0)
js.Global().Set("fibFunc", js.FuncOf(fibFunc))
<-done
}

假设调用 fibFunc 时,回调函数作为最后一个参数,那么通过 args[len(args)-1] 便可以获取到该函数。这与其他类型参数的传递并无区别。
使用 go func() 启动子协程,调用 fib 计算结果,计算结束后,调用回调函数 callback,并将计算结果传递给回调函数,使用 time.Sleep() 模拟 3s 的耗时操作。
计算结果出来前,先在界面上显示 Waiting 3s...

html

<html>
...
<body>
<input id="num" type="number" />
<button id="btn"BfwOnclick="fibFunc(num.value * 1, (v)=> ans.innerHTML=v)">Click</button>
<p id="ans"></p>
</body>
</html>

为 btn 注册了点击事件,第一个参数是待计算的数字,从 num 输入框获取。
第二个参数是一个回调函数,将参数 v 显示在 ans 文本框中。

网友回复

我知道答案,我要回答