go编译webassembly需js配合,仅goos=js goarch=wasm受支持;依赖系统调用的包不可用,须用syscall/js操作dom,且必须搭配版本匹配的wasm_exec.js运行。

Go 编译 WebAssembly 不是“直接跑浏览器里就能用 Go 写前端”的捷径,它本质是把 main 函数编译成 .wasm 文件,再靠 JS 胶水代码加载和桥接。没 JS 配合,WASM 模块自己什么都干不了。
Go 1.11+ 原生支持 WASM,但只限于 GOOS=js GOARCH=wasm
这是唯一受官方支持的 WASM 构建目标。不是所有 Go 代码都能无修改编译过去:
-
net/http、os/exec、database/sql等依赖系统调用的包会直接报错 —— 浏览器沙箱里没有文件系统、TCP 栈或进程管理 -
time.Sleep会被转为setTimeout,但阻塞式循环(如for {})会导致页面卡死,必须用runtime.GC()或syscall/js.Sleep让出控制权 - 标准库中仅
fmt、strings、encoding/json、sort等纯计算型包能安全使用
syscall/js 是唯一能操作 DOM 和事件的桥梁
Go 无法直接调用 document.getElementById,所有浏览器 API 必须通过 syscall/js 封装。常见写法:
js.Global().Get("console").Call("log", "Hello from Go!")
btn := js.Global().Get("document").Call("getElementById", "my-btn")
btn.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
js.Global().Get("alert").Invoke("Clicked!")
return nil
}))
注意两点:
- 回调函数必须用
js.FuncOf包裹,否则 GC 会回收闭包,导致后续调用崩溃 - JS 传入 Go 的参数是
js.Value类型,不能直接当 Go 字符串或数字用,需显式调用.String()或.Int() - Go 向 JS 返回值时,
nil表示undefined,返回结构体或 map 会自动序列化为 JS 对象(但不支持循环引用)
构建后必须用 index.html + wasm_exec.js 才能运行
Go 官方提供了一个胶水 JS 文件:$GOROOT/misc/wasm/wasm_exec.js。缺了它,浏览器加载 .wasm 会报 ReferenceError: go is not defined。
典型 HTML 结构:
<script src="wasm_exec.js"></script><script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
</script>
关键点:
-
wasm_exec.js必须和 Go 版本严格匹配(比如 Go 1.22 编译的 WASM,不能用 Go 1.21 的wasm_exec.js) -
instantiateStreaming在某些旧版 Edge 或 Safari 上不支持,得 fallback 到fetch().then(r => r.arrayBuffer()) - 如果用
go run直接启动本地服务(如go run main.go),它默认监听localhost:8080,但不会自动注入wasm_exec.js—— 你得自己配好静态文件服务
最常被忽略的是:WASM 模块初始化后,Go 的 main 函数只会执行一次;之后所有交互都靠 JS 主动调用 Go 导出函数,或者 Go 通过 js.FuncOf 注册回调。别指望它像服务端那样“一直运行”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











