Introducción: ¿Por qué Go y WebAssembly a nivel avanzado?
\n\nWebAssembly (Wasm) ha pasado de ser una tecnología experimental a un estándar para aplicaciones web de alto rendimiento. Go, con su compilación eficiente y soporte integrado para Wasm, es uno de los mejores lenguajes para crear módulos que funcionan al límite de las capacidades del navegador. Los ejemplos básicos de "¡Hola, mundo!" ya no son relevantes: las tareas modernas requieren una gestión precisa de la memoria, intercambio bidireccional de datos con JavaScript y optimización del tamaño del archivo binario.
\n\nEn este artículo, analizaremos técnicas avanzadas para trabajar con WebAssembly en Go. Aprenderá cómo transferir estructuras de datos complejas, llamar funciones de Go desde JavaScript y viceversa, así como optimizar el rendimiento de sus módulos Wasm. El material está dirigido a desarrolladores que ya están familiarizados con los conceptos básicos de Go y desean llevar sus habilidades al siguiente nivel.
\n\n1. Gestión de memoria: transferencia de cadenas, arreglos y estructuras
\n\nUno de los problemas clave al trabajar con WebAssembly es la transferencia de datos complejos entre la memoria lineal de Wasm y el montón de JavaScript. Go utiliza su propio recolector de basura, lo que impone limitaciones. Analicemos métodos avanzados.
\n\n1.1. Copia de cadenas mediante `copyBytesToGo` y `copyBytesToJS`
\n\nLa transferencia directa de cadenas a través de parámetros de función no es eficiente para grandes volúmenes de datos. En su lugar, utilice los métodos del paquete 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 // Obtenemos la longitud de la cadena\n length := jsStr.Get("length").Int()\n // Creamos un buffer en la memoria de Go\n buf := make([]byte, length)\n // Copiamos los datos de JS a Go\n js.CopyBytesToGo(buf, jsStr)\n // Ahora buf contiene los bytes UTF-8 de la cadena\n // ... procesamiento ...\n // Devolvemos el resultado\n result := []byte("Respuesta: " + 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. Transferencia de estructuras mediante serialización JSON
\n\nPara estructuras anidadas complejas, use JSON como formato intermedio. Esto es más lento que trabajar directamente con la memoria, pero significativamente más conveniente:
\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": "sin datos"})\n }\n // Obtenemos la cadena JSON desde 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 // Procesamiento de datos\n user.Age++\n // Devolvemos el JSON\n result, _ := json.Marshal(user)\n return js.ValueOf(string(result))\n}\n\n\n2. Comunicación bidireccional: llamar a JavaScript desde Go y viceversa
\n\nLa integración avanzada requiere que el módulo Go no solo pueda responder a llamadas, sino también iniciar acciones en el entorno de JavaScript.
\n\n2.1. Registro de callbacks de Go para JavaScript
\n\nUse js.FuncOf para crear funciones visibles desde JS. Es importante gestionar correctamente su ciclo de vida:
var callback js.Func\n\nfunc registerCallback() {\n callback = js.FuncOf(func(this js.Value, args []js.Value) interface{} {\n // Obtenemos el callback desde JS\n jsCallback := args[0]\n // Lo invocamos con datos desde Go\n go func() {\n // Simulamos trabajo asíncrono\n time.Sleep(100 * time.Millisecond)\n jsCallback.Invoke(js.ValueOf("Resultado desde Go"))\n }()\