综合 Goja,一个在Golang中嵌入JavaScript的运行时库

2024-11-19 03:33:49 +0800 CST views 1041

Goja,一个在Golang中嵌入JavaScript的运行时库

本文探讨了 Golang 生态系统中的 JavaScript 运行时库 Goja[^1]。Goja 作为一个在 Go 应用程序中嵌入 JavaScript 的强大工具脱颖而出,在操作数据和提供无需 go build 步骤的 SDK 方面具有独特优势。

背景:为什么需要 Goja

在我的项目中,在查询和操作大型数据集时遇到了挑战。最初,所有内容都是用 Go 编写的,这很高效,但在处理复杂的 JSON 响应时变得麻烦。虽然 Go 的极简主义方法通常是有利的,但特定任务所需的冗长性降低了我的速度。

使用嵌入式脚本语言可以简化这个过程,这促使我探索各种选择。Lua 是我的首选,因为它以轻量级和可嵌入而闻名。但我很快发现,Go 中可用的 Lua 库在实现、版本(5.1、5.2 等)和活跃支持方面都各不相同。

随后我调查了 Go 生态系统中其他流行的脚本语言,如 Expr[^2]、V8[^3] 和 Starlark[^4],最终 Goja 成为了最有前途的候选者。

这里是 GitHub 仓库[^5],我在其中对这些库进行了基准测试,测试它们的性能和与 Go 的集成便利性。

为什么选择 Goja?

Goja 之所以赢得我的青睐,是因为它与 Go 结构体的无缝集成。当你将 Go 结构体分配给 JavaScript 运行时中的值时,Goja 会自动推断字段和方法,使它们在 JavaScript 中可访问,而无需单独的桥接层。它利用 Go 的反射能力来调用这些字段的 getter 和 setter,提供了 Go 和 JavaScript 之间强大而透明的交互。

示例 1: 分配和返回值

让我们看一个简单的例子,将一个整数数组从 Go 传递到 JavaScript 运行时,并过滤出偶数值。

package main

import (
    "fmt"
    "github.com/dop251/goja"
)

func main() {
    vm := goja.New()

    // 将 Go 切片分配给 JavaScript 变量
    err := vm.Set("numbers", []int{1, 2, 3, 4, 5})
    if err != nil {
        panic(err)
    }

    // 在 JavaScript 中执行过滤操作
    v, err := vm.RunString(`
        numbers.filter(n => n % 2 === 0)
    `)
    if err != nil {
        panic(err)
    }

    // 将结果转换回 Go 切片
    result := v.Export().([]interface{})
    fmt.Println(result) // 输出: [2 4]
}

在这个例子中,Goja 能够根据其内容推断数组的类型,得益于 Go 的反射机制。过滤值并返回结果时,Goja 将结果转换回空接口数组 ([]interface{}),这为 Go 中处理 JavaScript 的动态类型提供了便利。

示例 2: 结构体和方法调用

接下来,让我们探讨 Goja 如何处理 Go 结构体,特别是方法和导出字段。

package main

import (
    "fmt"
    "github.com/dop251/goja"
)

type Person struct {
    Name string
    age  int
}

func (p *Person) GetName() string {
    return p.Name
}

func main() {
    vm := goja.New()

    person := &Person{Name: "Alice", age: 30}
    err := vm.Set("person", person)
    if err != nil {
        panic(err)
    }

    v, err := vm.RunString(`
        person.Name + " is " + person.GetName()
    `)
    if err != nil {
        panic(err)
    }

    fmt.Println(v.Export()) // 输出: Alice is Alice
}

在这个例子中,Goja 可以透明地访问 Go 结构体的导出字段和方法。未导出的字段 age 无法从 JavaScript 中访问,但导出的 Name 字段和 GetName 方法是可以使用的。

异常处理

当 JavaScript 中发生异常时,Goja 使用标准的 Go 错误处理机制。以下示例展示了如何捕获 JavaScript 中的运行时异常——例如除以零。

package main

import (
    "fmt"
    "github.com/dop251/goja"
)

func main() {
    vm := goja.New()

    _, err := vm.RunString(`
        1 / 0
    `)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        // 输出: Error: RangeError: Division by zero at <eval>:2:9(1)
    }
}

Goja 的错误值类型为 *goja.Exception,提供了有关 JavaScript 异常及其发生位置的信息。对于特定情况,Goja 还可以引发其他类型的异常,如 *goja.StackOverflowError*goja.CompilerSyntaxError,它们有助于处理执行 JavaScript 代码时的特殊问题。

使用 VM 池沙箱化用户代码

初始化 Goja 虚拟机 (VM) 的开销较大。为此,我们可以使用 Go 的 sync.Pool 来优化性能。以下是使用 sync.Pool 的一个简单示例:

package main

import (
    "fmt"
    "github.com/dop251/goja"
    "sync"
)

var vmPool = sync.Pool{
    New: func() interface{} {
        return goja.New()
    },
}

func main() {
    vm := vmPool.Get().(*goja.Runtime)
    defer vmPool.Put(vm)

    v, err := vm.RunString(`
        var result = 42;
        result;
    `)
    if err != nil {
        panic(err)
    }

    fmt.Println(v.Export()) // 输出: 42
}

使用 sync.Pool 时,需注意 VM 的全局命名空间问题。为解决这个问题,我们可以将用户代码封装在匿名函数中运行,避免命名冲突。

package main

import (
    "fmt"
    "github.com/dop251/goja"
    "sync"
)

var vmPool = sync.Pool{
    New: func() interface{} {
        return goja.New()
    },
}

func runUserCode(userCode string) (interface{}, error) {
    vm := vmPool.Get().(*goja.Runtime)
    defer vmPool.Put(vm)

    v, err := vm.RunString(fmt.Sprintf(`
        (function() {
            %s
        })();
    `, userCode))

    if err != nil {
        return nil, err
    }

    return v.Export(), nil
}

func main() {
    userCode := `
        var x = 10;
        var y = 20;
        return x + y;
    `

    result, err := runUserCode(userCode)
    if err != nil {
        panic(err)
    }

    fmt.Println(result) // 输出: 30
}

结论

Goja 提供了一种灵活且高效的方式来在 Go 程序中处理 JavaScript 脚本任务。它无缝的 Go 集成和动态特性极大地提升了处理复杂任务的效率。通过使用 Goja,你可以在不牺牲性能的情况下优化代码执行和用户体验。

Goja GitHub 仓库

Expr GitHub 仓库

V8 GitHub 仓库

Starlark GitHub 仓库

复制全文 生成海报 编程 Golang JavaScript 开发工具 性能优化

推荐文章

Boost.Asio: 一个美轮美奂的C++库
2024-11-18 23:09:42 +0800 CST
微信内弹出提示外部浏览器打开
2024-11-18 19:26:44 +0800 CST
MySQL数据库的36条军规
2024-11-18 16:46:25 +0800 CST
MySQL设置和开启慢查询
2024-11-19 03:09:43 +0800 CST
Vue3 结合 Driver.js 实现新手指引
2024-11-18 19:30:14 +0800 CST
前端如何给页面添加水印
2024-11-19 07:12:56 +0800 CST
js迭代器
2024-11-19 07:49:47 +0800 CST
JavaScript 策略模式
2024-11-19 07:34:29 +0800 CST
html夫妻约定
2024-11-19 01:24:21 +0800 CST
Vue 3 中的 Watch 实现及最佳实践
2024-11-18 22:18:40 +0800 CST
Hypothesis是一个强大的Python测试库
2024-11-19 04:31:30 +0800 CST
实用MySQL函数
2024-11-19 03:00:12 +0800 CST
SQL常用优化的技巧
2024-11-18 15:56:06 +0800 CST
Mysql允许外网访问详细流程
2024-11-17 05:03:26 +0800 CST
12个非常有用的JavaScript技巧
2024-11-19 05:36:14 +0800 CST
JavaScript设计模式:适配器模式
2024-11-18 17:51:43 +0800 CST
程序员茄子在线接单