本文将展示如何在 Go 中使用 WebAssembly。本文一起来学习如何从 Go 代码构建到 WebAssembly,通过VUE来展示使用 WebAssembly 的API。
本文涉及的 Go 需要 Go1.11 或更高版本的 Go 开发环境,这里将忽略 Go 环境的配置。前端将使用 VUE2 来构建。
文章涉及代码:github.com/QuintionTan…
什么是 WebAssembly?
WebAssembly(wasm)是指一种可以在浏览器及其外围技术和工具中运行的编程语言。它以二进制格式表示并由堆栈机器实现处理。与 JavaScript 一样,它由浏览器直接解释,但正在开发和规范,目标是在速度方面超越 JavaScript。
WebAssembly 大多由 C、C++、Rust 等各种高级语言编译而成,而不是程序员直接编写二进制代码。同样在 Go 中,将 Go 代码编译为 WebAssembly 的功能从 Go1.11正式添加为 Go 的标准功能。
更多内容如下:
HelloWorld
Go 有一个叫做交叉编译的特性。不仅可以为正在编译的机器的体系结构和操作系统构建二进制文件,还可以为其他体系结构和操作系统构建二进制文件。
例如,在 macOS 上为 Windows 和 Linux 交叉编译二进制文件非常容易。可以通过指定以下环境 GOOS
变量来像往常一样进行交叉编译:GOARCH go bulid
。
# 为 Windows 编译(32 位) $ GOOS=windows GOARCH=386 go build # 为 Linux 编译(64 位) $ GOOS=linux GOARCH=amd64 go build
创建目录 go-webassembly
,进入目录,再创建 helloworld
,进入 helloworld
目录,执行命令:
go mod init go-webassembly/helloworld
创建文件 main.go
,代码如下:
package main func main() { println("Hello, WebAssembly!") }
前端实现将使用 VUE 框架来展示其调用效果,因此需要创建文件夹
vue
,将把 WebAssembly 生成的wasm
和js
文件存储到项目目录public/wasms
。
现在以交叉方式编译 WebAssembly,在目录下并执行如下命令。请注意,此处将输出文件名指定为选项,但即使不指定也可以构建。GOOS js GOARCH wasm go build-o
GOOS=js GOARCH=wasm go build -o ../vue/public/wasms/helloworld/main.wasm
执行完命令后,将在目录下生成文件 main.wasm
,同时将在 GOROOT
目录下生成 wasm_exec.js
文件,完整路径为 /usr/local/go/misc/wasm
,将文件复制到路径 /vue/public/wasms/helloworld/
下,命令如下:
cp /usr/local/go/misc/wasm/wasm_exec.js . cp /usr/local/go/misc/wasm/wasm_exec_node.js . cp /usr/local/go/misc/wasm/wasm_exec.html .
这样目录 /vue/public/wasms/helloworld
就有两个文件 js
和 wasm
,接下来执行命令:
node wasm_exec_node.js main.wasm
输出的结果如下:
Hello, WebAssembly!
接下来就是在 VUE 中来展示 Helloworld
的调用,在 public/index.html
中引入JS,如下:
<script src="./wasms/helloworld/wasm_exec.js"></script>
构建组件 Helloworld,完整代码如下:
<template> <div class="card"> <div class="card-header"> <h4>Helloworld</h4> </div> <div class="card-body"> <p class="text-muted"> 点击“运行”,在控制台输出日志 <code>Hello, WebAssembly!</code> </p> <div class="live-preview"> <button @click="run()" class="btn btn-success" id="runButton" disabled > 运行 </button> </div> </div> </div> </template> <script> export default { name: "Helloworld", data() { return { go: null, mod: null, inst: null, }; }, mounted() { this.init(); }, methods: { init() { if (!WebAssembly.instantiateStreaming) { WebAssembly.instantiateStreaming = async ( resp, importObject ) => { const source = await (await resp).arrayBuffer(); return await WebAssembly.instantiate(source, importObject); }; } const go = new window.Go(); this.go = go; WebAssembly.instantiateStreaming( fetch("/wasms/helloworld/main.wasm"), go.importObject ) .then((result) => { console.log(result); this.mod = result.module; this.inst = result.instance; document.getElementById("runButton").disabled = false; }) .catch((err) => { console.error(err); }); }, async run() { console.clear(); await this.go.run(this.inst); this.inst = await WebAssembly.instantiate( this.mod, this.go.importObject ); }, }, };
</script>
点击按钮“运行”,在浏览器控制台输入如下:
处理 JavaScript 对象
接下来,学习如何在 JavaScript 中使用对象,为了更好的处理 Go 中的 JavaScript 对象,将使用 Go1.11 标准包中包含的包 syscall/js
。
syscall/js
里面定义了个新的类型 js.Value
,它表示一个JavaScript值,它提供了一个简单的API来操纵任何类型的JavaScript值并与之交互。一个 js.ValueOf()
函数,它接受任何 Go 基本类型并返回相应的 js.Value
。
Go值和 JavaScript 值对应关系如下:
Go | JavaScript |
js.Value | JavaScript 中的任何值 |
js.Func | function |
nil | null |
bool | Boolean |
integers 和 floats | Number |
string | String |
[]interface{} | new array |
map[string]interface{} | new object |
JavaScript 中的 js.Type
类型表示为类型。js.Type
类型定义如下,可以从 js.Value
类型的方法中检索。
type Type int const ( TypeUndefined Type = iota TypeNull TypeBoolean TypeNumber TypeString TypeSymbol TypeObject TypeFunction )
js.Value
类型将所有 JavaScript 值表示为单一类型,因此如果每个方法都调用了一个意外的值,panic
就会导致崩溃。例如,Int
方法可以 js.Value
将类型的值视为数字并将 int
值作为 Go 类型检索。但是,js.Value
类型也可以处理函数和字符串值,所以当调用一个不是数字的值时,panic
会发生错误。
因此,js.Type
通过使用 type
值,js.Value
可以处理 type
值的具体类型,避免 panic
。例如,对于Int
方法,最好只在 Type
方法返回 js.TypeNumber
时调用。如下:
func printNumber(v js.Value) { if v.Type() == js.TypeNumber { fmt.Printf("%d\n", v.Int()) } }
DOM 操作
可以在 Go 中使用 js.Value
来更好的操作 HTML Dom 对象。接下来创建目录 docments
,创建文件 main.go ,代码如下:
package main import "syscall/js" func main() { // 获取全局对象(网页浏览器为window) window := js.Global() // window.document.getElementById("helloresult") message := window.Get("document").Call("getElementById", "helloresult") // HTML message.Set("innerHTML", "Hello, WebAssembly") }
接下来在前端创建一个 id="helloresult"
的 DOM 对象。
按照上面的流程,生成 wasm
文件:
GOOS=js GOARCH=wasm go build -o ../vue/public/wasms/document/main.wasm
事件处理
上面介绍了如何操作 DOM,现在来实现时间的处理。
package main import "syscall/js" func main() { window := js.Global() // window.document.getElementById("clickresult") message := window.Get("document").Call("getElementById", "clickresult") cb := js.FuncOf(func(this js.Value, args []js.Value) any { message.Set("innerHTML", "Go 事件触发") return nil }) // message.addEventListener("click", cb) message.Call("addEventListener", "click", cb) select {} }
按照上面的流程,生成 wasm
文件:
GOOS=js GOARCH=wasm go build -o ../vue/public/wasms/events/main.wasm
总结
本文介绍了如何将 Go 代码构建为 WebAssembly、如果实现 Go 与 JavaScript 对象及回调函数。