Go与WebAssembly

Richard · · 1499 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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

Hello world

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

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

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

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

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

// main.go
package main
import "fmt"

func main()  {
	fmt.Println("hello world")
}

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

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

基本API

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

type Callback
    func NewCallback(fn func(args []Value)) Callback
    func NewEventCallback(flags EventCallbackFlag, fn func(event Value)) Callback
    func (c Callback) Release()
type Error
    func (e Error) Error() string
type EventCallbackFlag
type Type
    func (t Type) String() string
type TypedArray
    func TypedArrayOf(slice interface{}) TypedArray
    func (a TypedArray) Release()
type Value
    func Global() Value
    func Null() Value
    func Undefined() Value
    func ValueOf(x interface{}) Value
    func (v Value) Bool() bool
    func (v Value) Call(m string, args ...interface{}) Value
    func (v Value) Float() float64
    func (v Value) Get(p string) Value
    func (v Value) Index(i int) Value
    func (v Value) InstanceOf(t Value) bool
    func (v Value) Int() int
    func (v Value) Invoke(args ...interface{}) Value
    func (v Value) Length() int
    func (v Value) New(args ...interface{}) Value
    func (v Value) Set(p string, x interface{})
    func (v Value) SetIndex(i int, x interface{})
    func (v Value) String() string
    func (v Value) Type() Type
type ValueError
    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中的类型对应关系:

| Go                     | JavaScript             |
| ---------------------- | ---------------------- |
| js.Value               | [its value]            |
| js.TypedArray          | typed array            |
| js.Callback            | function               |
| nil                    | null                   |
| bool                   | boolean                |
| integers and floats    | number                 |
| string                 | string                 |
| []interface{}          | new array              |
| map[string]interface{} | new object             |


与JS互操作

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

// main.go
package main

import "syscall/js"


func main()  {
	alert := js.Global().Get("alert")
	alert.Invoke("hello world")
}

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


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

// main.go

import (
	"syscall/js"
)

var (
	done = make(chan string)
)

func main()  {
	sayHelloCallback := js.NewCallback(sayHelloFunc)
	defer sayHelloCallback.Release()
	sayHello := js.Global().Get("sayHello")
	sayHello.Invoke(sayHelloCallback)
	<- done
}

func sayHelloFunc(args []js.Value)  {
	alert := js.Global().Get("alert")
	alert.Invoke(args[0].String())
	done <- "done"
}


// index.js
function sayHello(callback) {
    callback("hello world");
}

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

  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,理解起来非常轻松

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

// main.go
package main

import (
	"fmt"
	"syscall/js"
)

var (
	count = 0
	done = make(chan string)
)

func main()  {
	sayMessageCallback := js.NewCallback(sayMessageFunc)
	defer sayMessageCallback.Release()
	sayMessage := js.Global().Get("sayMessage")
	sayMessage.Invoke(sayMessageCallback)

	unbindCallback := js.NewEventCallback(0, unbindFunc)
	defer unbindCallback.Release()
	addEventListener := js.Global().Get("addEventListener")
	addEventListener.Invoke("beforeunload", unbindCallback)

	<- done
}

func sayMessageFunc(args []js.Value) {
	fmt.Println(args[0].String(), count)
	count++
}

func unbindFunc(event js.Value) {
	done <- "done"
}


// index.js
let sayHello;

function sayMessage(callback) {
    sayHello = callback;
}


<button onclick="sayHello('hello world')">click</button>

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


有疑问加站长微信联系(非本文作者)

本文来自:知乎专栏

感谢作者:Richard

查看原文:Go与WebAssembly

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1499 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传