引言:为什么需要高级水平的 Go 和 WebAssembly?
\n\nWebAssembly (Wasm) 已从一项实验性技术转变为高性能 Web 应用的标准。Go 凭借其高效的编译和对 Wasm 的内置支持,成为创建在浏览器能力极限下运行的模块的最佳语言之一。基础的 "Hello, World!" 示例已不再适用——现代任务需要精细的内存管理、与 JavaScript 的双向数据交换以及二进制文件大小的优化。
\n\n在本文中,我们将深入探讨使用 Go 进行 WebAssembly 开发的高级技术。您将学习如何传递复杂的数据结构、如何从 JavaScript 调用 Go 函数以及反之亦然,以及如何优化 Wasm 模块的性能。本文面向已经熟悉 Go 基础知识并希望将技能提升到新水平的开发者。
\n\n1. 内存管理:传递字符串、数组和结构体
\n\n使用 WebAssembly 时的一个关键问题是在 Wasm 的线性内存和 JavaScript 的堆之间传递复杂数据。Go 使用自己的垃圾回收器,这带来了一些限制。让我们探讨一些高级方法。
\n\n1.1. 通过 `copyBytesToGo` 和 `copyBytesToJS` 复制字符串
\n\n对于大量数据,通过函数参数直接传递字符串效率不高。相反,应使用 syscall/js 包中的方法:
package main\n\nimport (\n "syscall/js"\n "unsafe"\n)\n\n//export processLargeString\nfunc processLargeString(this js.Value, args []js.Value) interface{} {\n if len(args) < 1 {\n return nil\n }\n jsStr := args[0]\n // 获取字符串长度\n length := jsStr.Get("length").Int()\n // 在 Go 内存中创建缓冲区\n buf := make([]byte, length)\n // 将数据从 JS 复制到 Go\n js.CopyBytesToGo(buf, jsStr)\n // 现在 buf 包含字符串的 UTF-8 字节\n // ... 处理 ...\n // 将结果返回\n result := []byte("答案: " + string(buf))\n dst := js.Global().Get("Uint8Array").New(len(result))\n js.CopyBytesToJS(dst, result)\n return dst\n}\n\nfunc main() {\n c := make(chan struct{}, 0)\n js.Global().Set("processLargeString", js.FuncOf(processLargeString))\n <-c\n}\n\n\n1.2. 通过 JSON 序列化传递结构体
\n\n对于复杂的嵌套结构体,使用 JSON 作为中间格式。这比直接操作内存慢,但方便得多:
\n\n//export processUserData\nfunc processUserData(this js.Value, args []js.Value) interface{} {\n if len(args) < 1 {\n return js.ValueOf(map[string]interface{}{"error": "无数据"})\n }\n // 从 JS 获取 JSON 字符串\n jsonStr := args[0].String()\n var user User\n if err := json.Unmarshal([]byte(jsonStr), &user); err != nil {\n return js.ValueOf(map[string]interface{}{"error": err.Error()})\n }\n // 处理数据\n user.Age++\n // 返回 JSON\n result, _ := json.Marshal(user)\n return js.ValueOf(string(result))\n}\n\n\n2. 双向通信:从 Go 调用 JavaScript 以及反之亦然
\n\n高级集成要求 Go 模块不仅能响应调用,还能在 JavaScript 环境中发起操作。
\n\n2.1. 为 JavaScript 注册 Go 回调函数
\n\n使用 js.FuncOf 创建从 JS 可见的函数。正确管理它们的生命周期很重要:
var callback js.Func\n\nfunc registerCallback() {\n callback = js.FuncOf(func(this js.Value, args []js.Value) interface{} {\n // 从 JS 获取回调函数\n jsCallback := args[0]\n // 使用 Go 中的数据调用它\n go func() {\n // 模拟异步工作\n time.Sleep(100 * time.Millisecond)\n jsCallback.Invoke(js.ValueOf("来自 Go 的结果"))\n }()\