package jscriptx import "context" // CallStatic 调用脚本导出的 class 上的**静态方法**。 // // export default class PkgImportController { // static Startup() { store.Set("pkg.registry", "https://…") } // Execute(g) { … } // } // // _, err := script.CallStatic(ctx, "Startup") // // 它跟 Call 有两点不同,都是"静态"这个语义要求的: // // - **不构造实例**:constructor 不会跑。构造函数做的是"这一次调用的准备", // 跟脚本级的初始化无关,跑它只会白费一遍还可能有副作用。 // - **VM 用完就丢**,不回池。静态方法一般只在脚本生命周期里跑一两次, // 为它留一个 VM 不划算;而且它做的事通常是往 Go 侧扩展里写东西, // 那些副作用留在 Go 那边,VM 本身没有保留的价值。 // // 脚本没写这个静态方法时返回的错误可以被 errors.Is(err, ErrFuncNotFound) 匹配上 // ——生命周期钩子多半是可选的,用它来区分"没写"和"写了但炸了": // // if _, err := script.CallStatic(ctx, "Startup"); err != nil && // !errors.Is(err, ErrFuncNotFound) { // return err // 真的出错了 // } // // 静态方法里能用 ctx 带进来的作用域扩展(WithScope),这正是它的用武之地: // 把配置写进 store、往 Go 侧注册东西。但**它建的 JS 对象活不下来**—— // VM 一丢就没了,别指望后续调用能看到。 func (s *Script) CallStatic(ctx context.Context, fn string, args ...any) (any, error) { if fn == DefaultFunc { return nil, newError(KindNotFound, s.name, fn, ErrFuncNotFound, "CallStatic 要给一个静态方法名") } return callAny(ctx, staticTarget{s}, fn, args) } // HasStatic 判断脚本导出的 class 上有没有这个静态方法。 // // 它要建一个 VM 才能回答,所以别在热路径上反复调;只想"有就调"的话, // 直接 CallStatic 然后判 ErrFuncNotFound 更省。 func (s *Script) HasStatic(fn string) bool { if fn == DefaultFunc { return false } vm, err := s.borrowStatic(context.Background()) if err != nil { return false } _, _, _, ok := vm.lookup(fn) return ok && vm.ctor != nil } // staticTarget 让静态调用复用 invoke 那一整套(超时中断、panic 恢复、错误分类), // 只是换一种取 VM 的方式:不构造实例,用完丢弃。 type staticTarget struct{ s *Script } func (t staticTarget) owner() *Script { return t.s } func (t staticTarget) acquire(ctx context.Context) (*vmHandle, error) { return t.s.borrowStatic(ctx) } // finish 直接丢弃,不回池——见 CallStatic 的说明。 func (t staticTarget) finish(*vmHandle, bool) { t.s.dropped.Add(1) } // borrowStatic 造一个只装了 class、没有实例的 VM。 func (s *Script) borrowStatic(ctx context.Context) (*vmHandle, error) { if s.closed.Load() { return nil, newError(KindClosed, s.name, "", ErrClosed, "脚本已关闭") } var extra map[string]any if sc, ok := scopeOf(ctx); ok { extra = sc.vmGlobals() } rt, err := s.engine.newRuntime(s.name, extra) if err != nil { return nil, err } return s.load(ctx, rt, nil, true) }