Introduction: Why Go and WebAssembly at an Advanced Level?
\n\nWebAssembly (Wasm) has evolved from an experimental technology into a standard for high-performance web applications. Go, with its efficient compilation and built-in Wasm support, is one of the best languages for creating modules that operate at the edge of browser capabilities. Basic "Hello, World!" examples are no longer relevant — modern tasks require fine-grained memory management, bidirectional data exchange with JavaScript, and binary file size optimization.
\n\nIn this article, we will cover advanced techniques for working with WebAssembly in Go. You will learn how to pass complex data structures, call Go functions from JavaScript and vice versa, and how to optimize the performance of your Wasm modules. The material is intended for developers who are already familiar with the basics of Go and want to take their skills to the next level.
\n\n1. Memory Management: Passing Strings, Arrays, and Structures
\n\nOne of the key challenges when working with WebAssembly is passing complex data between Wasm's linear memory and the JavaScript heap. Go uses its own garbage collector, which imposes limitations. Let's look at advanced methods.
\n\n1.1. Copying Strings via `copyBytesToGo` and `copyBytesToJS`
\n\nDirectly passing strings through function parameters is inefficient for large data volumes. Instead, use the methods from the syscall/js package:
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 // Get the string length\n length := jsStr.Get("length").Int()\n // Create a buffer in Go memory\n buf := make([]byte, length)\n // Copy data from JS to Go\n js.CopyBytesToGo(buf, jsStr)\n // Now buf contains the UTF-8 bytes of the string\n // ... processing ...\n // Return the result back\n result := []byte("Response: " + 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. Passing Structures via JSON Serialization
\n\nFor complex nested structures, use JSON as an intermediate format. This is slower than direct memory manipulation but significantly more convenient:
\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": "no data"})\n }\n // Get JSON string from JS\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 // Process data\n user.Age++\n // Return JSON back\n result, _ := json.Marshal(user)\n return js.ValueOf(string(result))\n}\n\n\n2. Bidirectional Communication: Calling JavaScript from Go and Vice Versa
\n\nAdvanced integration requires that the Go module can not only respond to calls but also initiate actions in the JavaScript environment.
\n\n2.1. Registering Go Callbacks for JavaScript
\n\nUse js.FuncOf to create functions visible from JS. It is important to properly manage their lifecycle:
var callback js.Func\n\nfunc registerCallback() {\n callback = js.FuncOf(func(this js.Value, args []js.Value) interface{} {\n // Get callback from JS\n jsCallback := args[0]\n // Invoke it with data from Go\n go func() {\n // Emulate asynchronous work\n time.Sleep(100 * time.Millisecond)\n jsCallback.Invoke(js.ValueOf("Result from Go"))\n }()\