原型在 nashorn 中从 globalscope 更改为 enginescope

prototype changes from globalscope to enginescope in nashorn

我正在尝试将一些库预加载到全局范围内(例如 chai.js)。这改变了一些对象的原型,我意识到这适用于 ENGINE_SCOPE 但不适用于 GLOBAL_SCOPE。

最小示例:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
Bindings globalBindings = engine.createBindings();
engine.eval("Object.prototype.test = function(arg){print(arg);}", globalBindings);

//works as expected, printing "hello"
engine.getContext().setBindings(globalBindings, ScriptContext.ENGINE_SCOPE);
engine.eval("var x = {}; x.test('hello');");

//throws TypeError: null is not a function in <eval> at line number 1
engine.getContext().setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
engine.getContext().setBindings(globalBindings, ScriptContext.GLOBAL_SCOPE);
engine.eval("var x = {}; x.test('hello');");

是否有解决方法使其按预期工作,即更改从全局范围正确传播到引擎范围?

为什么你的代码不起作用

全局作用域只能用于简单的变量映射。例如:

ScriptContext defCtx = engine.getContext();
defCtx.getBindings(ScriptContext.GLOBAL_SCOPE).put("foo", "hello");

Object 位于引擎范围内,因此甚至不会搜索全局范围以查找与其相关的任何映射(在您的情况下为 Object.prototype.test)。

文档摘录:

The default context's ENGINE_SCOPE is a wrapped instance of ECMAScript "global" object - which is the "this" in top level script expressions. So, you can access ECMAScript top-level objects like "Object", "Math", "RegExp", "undefined" from this scope object. Nashorn Global scope object is represented by an internal implementation class called jdk.nashorn.internal.objects.Global. Instance of this class is wrapped as a jdk.nashorn.api.scripting.ScriptObjectMirror instance. ScriptObjectMirror class implements javax.script.Bindings interface. Please note that the context's GLOBAL_SCOPE Bindings and nashorn global object are different. Nashorn's global object is associated with ENGINE_SCOPE and not with GLOBAL_SCOPE. GLOBAL_SCOPE object of default script context is a javax.script.SimpleBindings instance.The user can fill it with name, value pairs from the java code.

https://wiki.openjdk.java.net/display/Nashorn/Nashorn+jsr223+engine+notes

解决方案

  1. 继续使用引擎作用域
  2. 通过在 Java 命令行中指定 -Dnashorn.args=--global-per-engine 来使用 --global-per-engine option。然后 Nashorn 将使用全局对象的单个实例进行所有脚本评估,无论是否传递了 ScriptContext。
  3. 使用成熟的 ScriptContext 而不是绑定:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
SimpleScriptContext context = new SimpleScriptContext();
engine.eval("Object.prototype.test = function(arg){print(arg);}", context);
engine.eval("var x = {}; x.test('hello');", context);

如何 运行 多个脚本,每次都加载库,但之前的执行没有留下任何东西

每次您需要一个带有库的新上下文时,只需创建它即可:

public static void main(String[] args) throws ScriptException {
    ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
    SimpleScriptContext context1 = createContextWithLibraries(engine);
    //works as expected, printing "hello"
    engine.eval("var x = {}; x.test('hello'); var y = 'world';", context1);
    SimpleScriptContext context2 = createContextWithLibraries(engine);
    //works as expected, printing "hello"
    engine.eval("var x = {}; x.test('hello');", context2);
    //works as expected, printing "world"
    engine.eval("print(y);", context1);
    //fails with exception since there is no "y" variable in context2
    engine.eval("print(y);", context2);
}

private static SimpleScriptContext createContextWithLibraries(ScriptEngine engine) throws ScriptException {
    SimpleScriptContext context = new SimpleScriptContext();
    engine.eval("Object.prototype.test = function(arg){print(arg);}", context);
    return context;
}

Denis 已经解释了这个问题,所以我不再重复他说的话。然而,我会给出另一种解决方案。为避免一遍又一遍地解析相同库的开销,您可以编译它们。这是一种方法:

import java.util.*;
import javax.script.*;

public class Test
{
    public static List<CompiledScript> compileScripts(ScriptEngine engine, String... scripts) throws ScriptException
    {
        Compilable compilable = (Compilable)engine;
        ArrayList<CompiledScript> list = new ArrayList<>();
        for(String script : scripts)
            list.add(compilable.compile(script));
        return list;
    }

    public static void execute(ScriptEngine engine, List<CompiledScript> libs, String script) throws ScriptException
    {
        engine.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
        for(CompiledScript lib : libs)
            lib.eval();
        engine.eval(script);
    }

    public static void main(String[] args) throws ScriptException
    {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("nashorn");
        List<CompiledScript> libs = compileScripts(engine, "var x = 1", "Object.prototype.test = function(arg){print(arg)}");

        // Prints 'hello'
        execute(engine, libs, "x.test('hello')");

        // Defines y
        execute(engine, libs, "var y = 2");

        // Checks that executions have a clean (non-polluted) context
        // Throws ReferenceError: "y" is not defined in <eval> at line number 1
        execute(engine, libs, "print(y)");
    }
}