使用 VUE 和 Go 触摸 WebAssembly
“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第 2 篇文章,点击查看活动详情”
本文将展示如何在 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 对象及回调函数。
转载自:https://juejin.cn/post/7139469118842863630