第五章:与 JavaScript 和 DOM 交互
5.1 syscall/js 包
Go-App 底层使用 Go 标准库的 syscall/js 包与 JavaScript 交互。
核心概念
import "syscall/js"
// js.Value 代表一个 JavaScript 值
global := js.Global() // 全局对象 (window)
document := global.Get("document") // document 对象
console := global.Get("console") // console 对象
常用操作
// 获取全局对象
window := js.Global()
// 获取 document
doc := window.Get("document")
// 调用 JavaScript 函数
console := window.Get("console")
console.Call("log", "Hello from Go!")
// 获取/设置属性
location := window.Get("location")
url := location.Get("href").String()
// 创建 JavaScript 对象
obj := js.Global().Get("Object").New()
obj.Set("name", "Go")
obj.Set("version", 1.18)
5.2 调用 JavaScript 函数
调用全局函数
// 调用 alert
app.Window().Call("alert", "Hello World!")
// 调用 confirm
result := app.Window().Call("confirm", "确定删除吗?").Bool()
if result {
fmt.Println("用户点击了确定")
}
// 调用 prompt
name := app.Window().Call("prompt", "请输入您的名字", "默认值").String()
fmt.Println("用户输入:", name)
调用 DOM 方法
// 获取元素
doc := app.Window().Get("document")
element := doc.Call("getElementById", "my-element")
// 修改样式
element.Get("style").Set("color", "red")
element.Get("style").Set("backgroundColor", "yellow")
// 添加/删除 class
element.Get("classList").Call("add", "active")
element.Get("classList").Call("remove", "hidden")
element.Get("classList").Call("toggle", "visible")
// 设置属性
element.Set("innerHTML", "<b>加粗文本</b>")
element.Set("textContent", "纯文本内容")
5.3 暴露 Go 函数给 JavaScript
func main() {
// 注册 Go 函数供 JavaScript 调用
js.Global().Set("goFunction", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// 处理参数
if len(args) > 0 {
param := args[0].String()
fmt.Println("收到参数:", param)
}
// 返回值
return "Hello from Go!"
}))
// 保持程序运行
select {}
}
带参数的函数
// 注册计算函数
js.Global().Set("calculate", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) < 2 {
return "需要两个参数"
}
a := args[0].Int()
b := args[1].Int()
result := map[string]interface{}{
"sum": a + b,
"product": a * b,
}
return result
}))
在 HTML 中调用
<script>
// 等待 WASM 加载完成
const go = new Go();
WebAssembly.instantiateStreaming(fetch("app.wasm"), go.importObject)
.then((result) => {
go.run(result.instance);
// 现在可以调用 Go 函数
const response = goFunction("test");
console.log(response);
// 调用计算函数
const calc = calculate(5, 3);
console.log(calc.sum); // 8
console.log(calc.product); // 15
});
</script>
5.4 操作 DOM
创建和插入元素
// 创建新元素
doc := app.Window().Get("document")
newDiv := doc.Call("createElement", "div")
newDiv.Set("textContent", "动态创建的元素")
newDiv.Get("style").Set("color", "blue")
// 插入到页面
body := doc.Get("body")
body.Call("appendChild", newDiv)
事件监听
// 获取元素
button := app.Window().Get("document").Call("getElementById", "my-button")
// 添加事件监听
button.Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
fmt.Println("按钮被点击!")
return nil
}))
5.5 使用第三方 JavaScript 库
加载外部脚本
type chartComponent struct {
app.Compo
}
func (c *chartComponent) OnMount(ctx app.Context) {
// 动态加载 Chart.js
script := app.Window().Get("document").Call("createElement", "script")
script.Set("src", "https://cdn.jsdelivr.net/npm/chart.js")
script.Set("onload", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// 脚本加载完成后初始化图表
ctx.Dispatch(func(ctx app.Context) {
c.initChart()
})
return nil
}))
app.Window().Get("document").Get("head").Call("appendChild", script)
}
func (c *chartComponent) initChart() {
// 使用 Chart.js 创建图表
canvas := app.Window().Get("document").Call("getElementById", "chart-canvas")
chartConfig := map[string]interface{}{
"type": "bar",
"data": map[string]interface{}{
"labels": []string{"A", "B", "C"},
"datasets": []map[string]interface{}{
{
"label": "数值",
"data": []int{10, 20, 30},
},
},
},
}
js.Global().Get("Chart").New(canvas, chartConfig)
}
func (c *chartComponent) Render() app.UI {
return app.Canvas().ID("chart-canvas")
}
5.6 处理 Promise
// 调用返回 Promise 的 JavaScript API
func fetchData(url string) {
// 使用 fetch API
promise := app.Window().Call("fetch", url)
promise.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
response := args[0]
return response.Call("json")
})).Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
data := args[0]
fmt.Println("收到数据:", data)
return nil
})).Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
err := args[0]
fmt.Println("错误:", err)
return nil
}))
}
5.7 处理 JavaScript 回调
// 设置定时器
func setTimeout() {
js.Global().Call("setTimeout", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
fmt.Println("2秒后执行")
return nil
}), 2000)
}
// 使用 requestAnimationFrame
func animate() {
var renderFrame js.Func
renderFrame = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// 执行动画帧
fmt.Println("动画帧")
// 递归调用
js.Global().Call("requestAnimationFrame", renderFrame)
return nil
})
js.Global().Call("requestAnimationFrame", renderFrame)
}
5.8 Web API 使用示例
Geolocation API
type locationComponent struct {
app.Compo
lat string
lng string
error string
}
func (l *locationComponent) OnMount(ctx app.Context) {
navigator := app.Window().Get("navigator")
geolocation := navigator.Get("geolocation")
success := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
position := args[0]
coords := position.Get("coords")
ctx.Dispatch(func(ctx app.Context) {
l.lat = fmt.Sprintf("%f", coords.Get("latitude").Float())
l.lng = fmt.Sprintf("%f", coords.Get("longitude").Float())
l.Update()
})
return nil
})
error := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
err := args[0]
ctx.Dispatch(func(ctx app.Context) {
l.error = err.Get("message").String()
l.Update()
})
return nil
})
geolocation.Call("getCurrentPosition", success, error)
}
func (l *locationComponent) Render() app.UI {
if l.error != "" {
return app.Div().Textf("错误: %s", l.error)
}
return app.Div().Body(
app.P().Textf("纬度: %s", l.lat),
app.P().Textf("经度: %s", l.lng),
)
}
LocalStorage 操作
// LocalStorage 包装器
type Storage struct {
storage js.Value
}
func NewStorage() *Storage {
return &Storage{
storage: js.Global().Get("localStorage"),
}
}
func (s *Storage) Set(key, value string) {
s.storage.Call("setItem", key, value)
}
func (s *Storage) Get(key string) string {
return s.storage.Call("getItem", key).String()
}
func (s *Storage) Remove(key string) {
s.storage.Call("removeItem", key)
}
func (s *Storage) Clear() {
s.storage.Call("clear")
}
5.9 最佳实践
内存管理
// 释放 js.Func 避免内存泄漏
var callback js.Func
callback = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// 使用完毕后释放
defer callback.Release()
// 处理逻辑
return nil
})
错误处理
func safeJSCall(fn func()) {
defer func() {
if r := recover(); r != nil {
fmt.Println("JavaScript 调用错误:", r)
}
}()
fn()
}
// 使用
safeJSCall(func() {
result := js.Global().Call("someFunction")
fmt.Println(result)
})
5.10 本章小结
在这一章中,我们学习了:
- ✅
syscall/js 包的基本使用 - ✅ 如何从 Go 调用 JavaScript 函数
- ✅ 如何将 Go 函数暴露给 JavaScript
- ✅ 直接操作 DOM 的方法
- ✅ 集成第三方 JavaScript 库
- ✅ 处理 Promise 和异步操作
- ✅ 使用 Web API(Geolocation 等)
注意事项:
- 尽量减少 Go 与 JavaScript 的边界调用,性能开销较大
- 及时释放
js.Func 避免内存泄漏 - 使用
defer recover() 处理 JavaScript 错误
下一步:学习构建 PWA 与部署。
练习
- 创建一个组件,使用
localStorage 存储用户偏好设置 - 集成一个第三方 JS 库(如 lodash 或 moment.js)到 Go-App 中
- 实现一个使用 Geolocation API 的"附近地点"功能
- 创建一个自定义的 JavaScript 桥接函数,实现 Go 与 JS 的双向通信
下一章:构建 PWA 与部署
#教程 #Go #GoApp #PWA #WebAssembly #小凯