golang实现ECMAScript 5.1+解释器的JavaScript引擎插件库goja的使用
Golang实现ECMAScript 5.1+解释器的JavaScript引擎插件库goja的使用
简介
goja是一个用纯Go语言实现的ECMAScript 5.1(+)解释器,重点在于标准合规性和性能。这个项目很大程度上受到了otto的启发。
最低要求的Go版本是1.20。
特性
- 完整的ECMAScript 5.1支持(包括正则表达式和严格模式)
- 通过了几乎所有tc39测试(针对目前已实现的功能)
- 能够运行Babel、Typescript编译器以及几乎所有用ES5编写的代码
- 支持Sourcemaps
- 大部分ES6功能(仍在开发中)
基本示例
运行JavaScript并获取结果值:
vm := goja.New()
v, err := vm.RunString("2 + 2")
if err != nil {
panic(err)
}
if num := v.Export().(int64); num != 4 {
panic(num)
}
传递值到JavaScript
任何Go值都可以使用Runtime.ToValue()方法传递给JS:
vm := goja.New()
vm.Set("myVar", 42)
res, _ := vm.RunString("myVar + 8")
fmt.Println(res.Export()) // 输出: 50
从JS导出值
JS值可以使用Value.Export()方法导出为其默认的Go表示形式,或者使用Runtime.ExportTo()方法导出到特定的Go变量。
从Go调用JS函数
有两种方法:
- 使用AssertFunction():
const SCRIPT = `
function sum(a, b) {
return +a + b;
}
`
vm := goja.New()
_, err := vm.RunString(SCRIPT)
if err != nil {
panic(err)
}
sum, ok := goja.AssertFunction(vm.Get("sum"))
if !ok {
panic("Not a function")
}
res, err := sum(goja.Undefined(), vm.ToValue(40), vm.ToValue(2))
if err != nil {
panic(err)
}
fmt.Println(res)
// 输出: 42
- 使用Runtime.ExportTo():
const SCRIPT = `
function sum(a, b) {
return +a + b;
}
`
vm := goja.New()
_, err := vm.RunString(SCRIPT)
if err != nil {
panic(err)
}
var sum func(int, int) int
err = vm.ExportTo(vm.Get("sum"), &sum)
if err != nil {
panic(err)
}
fmt.Println(sum(40, 2)) // 注意:函数中的_this_值将是undefined
// 输出: 42
映射结构体字段和方法名
默认情况下,名称按原样传递(即大写)。要匹配标准的JavaScript命名约定,可以使用FieldNameMapper:
vm := goja.New()
vm.SetFieldNameMapper(TagFieldNameMapper("json", true))
type S struct {
Field int `json:"field"`
}
vm.Set("s", S{Field: 42})
res, _ := vm.RunString(`s.field`) // 没有映射器的话会是s.Field
fmt.Println(res.Export())
// 输出: 42
异常处理
任何在JavaScript中抛出的异常都会作为*Exception类型的错误返回:
vm := goja.New()
_, err := vm.RunString(`
throw("Test");
`)
if jserr, ok := err.(*Exception); ok {
if jserr.Value().Export() != "Test" {
panic("wrong value")
}
} else {
panic("wrong type")
}
中断执行
func TestInterrupt(t *testing.T) {
const SCRIPT = `
var i = 0;
for (;;) {
i++;
}
`
vm := goja.New()
time.AfterFunc(200 * time.Millisecond, func() {
vm.Interrupt("halt")
})
_, err := vm.RunString(SCRIPT)
if err == nil {
t.Fatal("Err is nil")
}
// err是*InterruptError类型,其Value()方法返回传递给vm.Interrupt()的任何内容
}
完整示例
下面是一个完整的示例,展示了如何初始化goja引擎、执行脚本并与Go代码交互:
package main
import (
"fmt"
"github.com/dop251/goja"
)
func main() {
// 创建新的VM实例
vm := goja.New()
// 定义一个JavaScript函数
script := `
function greet(name) {
return "Hello, " + name + "!";
}
function add(a, b) {
return a + b;
}
`
// 执行脚本
_, err := vm.RunString(script)
if err != nil {
panic(err)
}
// 调用JavaScript中的greet函数
greet, ok := goja.AssertFunction(vm.Get("greet"))
if !ok {
panic("greet is not a function")
}
res, err := greet(goja.Undefined(), vm.ToValue("World"))
if err != nil {
panic(err)
}
fmt.Println(res) // 输出: Hello, World!
// 使用ExportTo调用add函数
var addFunc func(int, int) int
err = vm.ExportTo(vm.Get("add"), &addFunc)
if err != nil {
panic(err)
}
sum := addFunc(20, 22)
fmt.Println(sum) // 输出: 42
// 从Go传递对象到JavaScript
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
vm.Set("person", Person{Name: "Alice", Age: 30})
val, err := vm.RunString(`
"Name: " + person.name + ", Age: " + person.age
`)
if err != nil {
panic(err)
}
fmt.Println(val) // 输出: Name: Alice, Age: 30
}
这个示例展示了goja的基本用法,包括执行脚本、调用JS函数、在Go和JS之间传递数据等核心功能。
更多关于golang实现ECMAScript 5.1+解释器的JavaScript引擎插件库goja的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang实现ECMAScript 5.1+解释器的JavaScript引擎插件库goja的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Goja - Golang实现的JavaScript引擎
Goja是一个用纯Go语言实现的ECMAScript 5.1+解释器。它允许你在Go应用程序中嵌入JavaScript执行环境,非常适合需要脚本扩展功能的Go程序。
基本使用
安装
go get github.com/dop251/goja
简单示例
package main
import (
"fmt"
"github.com/dop251/goja"
)
func main() {
// 创建新的运行时环境
vm := goja.New()
// 执行简单JS代码
v, err := vm.RunString("2 + 2")
if err != nil {
panic(err)
}
fmt.Println(v) // 输出: 4
// 执行更复杂的JS代码
_, err = vm.RunString(`
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
`)
if err != nil {
panic(err)
}
// 调用JS函数
fact, ok := goja.AssertFunction(vm.Get("factorial"))
if !ok {
panic("Not a function")
}
res, err := fact(goja.Undefined(), vm.ToValue(5))
if err != nil {
panic(err)
}
fmt.Println(res) // 输出: 120
}
高级功能
Go与JS交互
package main
import (
"fmt"
"github.com/dop251/goja"
)
func main() {
vm := goja.New()
// 向JS环境注入Go函数
err := vm.Set("greet", func(call goja.FunctionCall) goja.Value {
name := call.Argument(0).String()
return vm.ToValue("Hello, " + name + "!")
})
if err != nil {
panic(err)
}
// 调用Go注入的函数
v, err := vm.RunString(`greet("World")`)
if err != nil {
panic(err)
}
fmt.Println(v) // 输出: Hello, World!
}
从Go访问JS对象
package main
import (
"fmt"
"github.com/dop251/goja"
)
func main() {
vm := goja.New()
// 执行JS代码创建对象
_, err := vm.RunString(`
var person = {
name: "Alice",
age: 30,
sayHello: function() {
return "Hi, I'm " + this.name;
}
}
`)
if err != nil {
panic(err)
}
// 获取JS对象
person := vm.Get("person")
obj := person.ToObject(vm)
// 访问属性
name := obj.Get("name").String()
age := obj.Get("age").ToInteger()
fmt.Printf("%s is %d years old\n", name, age) // Alice is 30 years old
// 调用方法
sayHello, ok := goja.AssertFunction(obj.Get("sayHello"))
if ok {
res, err := sayHello(person, nil)
if err != nil {
panic(err)
}
fmt.Println(res.String()) // Hi, I'm Alice
}
}
错误处理
package main
import (
"fmt"
"github.com/dop251/goja"
)
func main() {
vm := goja.New()
// 执行可能出错的代码
_, err := vm.RunString(`
function divide(a, b) {
if (b === 0) {
throw new Error("Division by zero");
}
return a / b;
}
`)
if err != nil {
panic(err)
}
divide, _ := goja.AssertFunction(vm.Get("divide"))
// 捕获JS抛出的异常
_, err = divide(goja.Undefined(), vm.ToValue(10), vm.ToValue(0))
if jsErr, ok := err.(*goja.Exception); ok {
fmt.Println("JS Error:", jsErr.String())
} else if err != nil {
panic(err)
}
}
性能考虑
- Goja不是性能最高的JavaScript引擎,但对于大多数嵌入场景已经足够
- 避免频繁在Go和JS之间传递数据
- 对于性能敏感的部分,尽量用Go实现
限制
- 不支持完整的ES6+特性(但支持部分)
- 没有JIT编译,纯解释执行
- 某些JavaScript特性可能实现不完全
Goja是一个轻量级的解决方案,特别适合需要在Go应用中添加脚本功能的场景,如配置脚本、规则引擎、插件系统等。