Go与WebAssembly

https://zhuanlan.zhihu.com/p/51335026
Richard主攻前端,伪全栈,gopher, rustacean

在之前的一篇文章《WebAssembly的过去,现在和未来》,简单的介绍了WASM的历史,现状以及即将带来的特性。这篇文章,将会简单的使用Go的API来生成WASM,通过与JS的交互来实现WASM的加载和执行。使用Go的原因也是个人的爱好,其非常简洁,拥有静态语言的可维护性及性能,同时还拥有动态语言的开发效率,灵活性(这篇文章要求你对Go有一定的了解,以后有机会也会写一些Go的入门文章)。

Hello world

1、首先需要安装Go1.11+的版本,WebAssembly在Go1.11得到支持

2、在Go编译时需要指定OS参数为js,ARCH参数为wasm,就像这样:

  1. GOARCH=wasm GOOS=js go build -o lib.wasm main.go

编译完成会在当前目录下生成lib.wasm二进制文件,通过JS来加载这个文件,就能执行WASM了。

3、main.go文件的内容是程序猿都懂的”hello world”:

  1. // main.go
  2. package main
  3. import "fmt"
  4. func main() {
  5. fmt.Println("hello world")
  6. }

4、使用Go官方提供的Html文件和JS文件来加载lib.wasm(这里需要注意的是lib.wasm文件的Content-Type必须是application/wasm,这里你可以通过启动一个服务器来解决)

然后我们可以看到控制台输出了 “hello world”





基本API

上面的hello world我们看完了,但是实际开发中肯定不是这样,需要操作DOM,监听事件等一系列操作,这个时候我们需要Go提供的专有API syscall/js包。简单的看一下go doc:

  1. type Callback
  2. func NewCallback(fn func(args []Value)) Callback
  3. func NewEventCallback(flags EventCallbackFlag, fn func(event Value)) Callback
  4. func (c Callback) Release()
  5. type Error
  6. func (e Error) Error() string
  7. type EventCallbackFlag
  8. type Type
  9. func (t Type) String() string
  10. type TypedArray
  11. func TypedArrayOf(slice interface{}) TypedArray
  12. func (a TypedArray) Release()
  13. type Value
  14. func Global() Value
  15. func Null() Value
  16. func Undefined() Value
  17. func ValueOf(x interface{}) Value
  18. func (v Value) Bool() bool
  19. func (v Value) Call(m string, args ...interface{}) Value
  20. func (v Value) Float() float64
  21. func (v Value) Get(p string) Value
  22. func (v Value) Index(i int) Value
  23. func (v Value) InstanceOf(t Value) bool
  24. func (v Value) Int() int
  25. func (v Value) Invoke(args ...interface{}) Value
  26. func (v Value) Length() int
  27. func (v Value) New(args ...interface{}) Value
  28. func (v Value) Set(p string, x interface{})
  29. func (v Value) SetIndex(i int, x interface{})
  30. func (v Value) String() string
  31. func (v Value) Type() Type
  32. type ValueError
  33. func (e *ValueError) Error() string

其实API非常的简洁,Value对应的就是JS中的各种数据类型,Callback是JS中的回调函数,其中包括NewCallback,NewEventCallback就能生成回调函数。接下来我们简单的介绍一下API:

  • js.Global().get: 可以获取window对象上的属性,包括全局变量,DOM API(例如 addEventListener,setInterval),然后我们就能操作变量,调用函数。
  • js.Value.Get() 和 js.Value.Set(): 可以获取和设定对象上的属性
  • js.Value.Index() 和 js.Value.SetIndex(): 可以获取和设定数组上的元素
  • js.Value.Call(): 可以执行对象上的方法
  • js.Value.Invoke: 可以执行全局函数,例如addEventListener

还有另外一些,包括:

  • js.Undefined(): 可以返回JS中的undefined
  • js.Null(): 可以返回JS中的null
  • js.ValueOf(): 接受Go的基本类型,返回对应JS类型的值

了解完API,还需要知道Go与JS中的类型对应关系:

  1. | Go | JavaScript |
  2. | ---------------------- | ---------------------- |
  3. | js.Value | [its value] |
  4. | js.TypedArray | typed array |
  5. | js.Callback | function |
  6. | nil | null |
  7. | bool | boolean |
  8. | integers and floats | number |
  9. | string | string |
  10. | []interface{} | new array |
  11. | map[string]interface{} | new object |

与JS互操作

在了解完API和基本的数据类型后,我们来简单的联系练习一下:

  1. // main.go
  2. package main
  3. import "syscall/js"
  4. func main() {
  5. alert := js.Global().Get("alert")
  6. alert.Invoke("hello world")
  7. }

这段代码非常简单,只有两行:通过js.Global().Get()拿到全局alert函数的引用,然后调用alert.Invoke来调用alert函数,跟Java的反射非常的像。

接下来我们来一个复杂一点例子:

  1. // main.go
  2. import (
  3. "syscall/js"
  4. )
  5. var (
  6. done = make(chan string)
  7. )
  8. func main() {
  9. sayHelloCallback := js.NewCallback(sayHelloFunc)
  10. defer sayHelloCallback.Release()
  11. sayHello := js.Global().Get("sayHello")
  12. sayHello.Invoke(sayHelloCallback)
  13. <- done
  14. }
  15. func sayHelloFunc(args []js.Value) {
  16. alert := js.Global().Get("alert")
  17. alert.Invoke(args[0].String())
  18. done <- "done"
  19. }
  1. // index.js
  2. function sayHello(callback) {
  3. callback("hello world");
  4. }

这段代码复杂了一点,我们一点一点的分析:

  1. js.NewCallback创建了一个回调函数,回调函数的参数是Go的函数sayHelloFunc
  2. sayHelloFunc接收的参数args[0], 全局JS函数sayHello中callback传递过来的参数”hello world”,然后alert 这个参数
  3. sayHelloFunc执行完成后会向channel中传递一个值“done”,通知主Goroutine可以回收了,不然的话,main函数就会立即执行完成,不会等待回调函数触发的那一刻
  4. 调用 defer sayHelloCallback.Release(),在main函数执行完成之前释放回调函数占用的资源
  5. 通过js.Global.Get(“sayHello”),拿到下面可以看到的全局JS 函数sayHello
  6. 执行sayHello函数,传递前面定义的sayHelloCallback作为该函数参数,对应的就是sayHello的callback参数

如果你熟悉Go和JS,理解起来非常轻松

最后,我们看一个更加复杂的例子:

  1. // main.go
  2. package main
  3. import (
  4. "fmt"
  5. "syscall/js"
  6. )
  7. var (
  8. count = 0
  9. done = make(chan string)
  10. )
  11. func main() {
  12. sayMessageCallback := js.NewCallback(sayMessageFunc)
  13. defer sayMessageCallback.Release()
  14. sayMessage := js.Global().Get("sayMessage")
  15. sayMessage.Invoke(sayMessageCallback)
  16. unbindCallback := js.NewEventCallback(0, unbindFunc)
  17. defer unbindCallback.Release()
  18. addEventListener := js.Global().Get("addEventListener")
  19. addEventListener.Invoke("beforeunload", unbindCallback)
  20. <- done
  21. }
  22. func sayMessageFunc(args []js.Value) {
  23. fmt.Println(args[0].String(), count)
  24. count++
  25. }
  26. func unbindFunc(event js.Value) {
  27. done <- "done"
  28. }
  1. // index.js
  2. let sayHello;
  3. function sayMessage(callback) {
  4. sayHello = callback;
  5. }
  1. <button onclick="sayHello('hello world')">click</button>

这里就不具体讲每一段代码分别代表什么含义了,完整的代码已经上传到github:anymost/Go-WebAssembly

ft_authoradmin  ft_create_time2019-06-12 11:11
 ft_update_time2019-06-12 11:12