6

我需要以某种方式深度克隆我的ScriptEngine对象的整个绑定集。

我试过的

  • 到目前为止,我已经尝试使用Cloner 库来克隆整个 Bindings 结构。如果它有效,那就太好了,因为它可以确保精确的副本,包括私有变量。但这会导致 jvm 堆损坏(jvm 只是崩溃,退出代码为 -1073740940)。有时它不会崩溃,但会发生奇怪的事情,比如System.out.println()停止正常工作......

  • 我还研究了使用 ScriptEngine 中的 js 代码克隆对象,以便我可以将它们作为 NativeObjects 获取并在一些 java 映射中管理它们。但我发现的所有克隆方法都有缺陷。我想要对象的精确快照。例如,如果两个对象 a 和 b 中的每一个都包含引用同一对象 c 的字段(例如 a.fa 和 b.fb),则当使用(jQuery.extend()例如)克隆时,克隆的 a 和 b 将引用不同的克隆c,而不是引用一个相同的克隆。以及许多其他边缘问题。a.fab.fb

  • 我还尝试使用 Cloner 克隆整个 ScriptEngine(不仅是绑定),还尝试使用 Rhino 的 js 引擎并克隆整个范围(而不是捆绑的 ScriptEngine 包装器)。但是堆损坏问题仍然存在。

为什么我需要这样做

我需要这个,因为我必须能够将整个 ScriptEngine 绑定的值恢复到以前的某个点。我需要制作绑定的精确快照。

该应用程序是我的博士研究项目的一部分,该项目包括运行带有节点(在 java 中实现)的状态机,这些节点附加了 js 代码。js 代码由最终用户输入,并在运行时进行评估。当无法通过路径到达最终状态时,算法会向后退一步,试图找到替代路径。每向后退一步,它都必须撤消 js 引擎绑定中可能发生的任何更改。


所有全局变量名称在 js eval-ing 之前都是已知的,并且是对象(用户为节点键入代码,然后(在 java 中)将其组织成具有特定名称模式的 js 对象)。但是它们的内容可以是任何内容,因为这是由用户 js 代码控制的。

所以我想我现在唯一的解决方案是使用 js 代码克隆 js 对象。

4

2 回答 2

3

除了“边缘情况”之外,jQuery.extend 还可以按照您提到的方式使用。a b并且它们的克隆都将引用同一个对象c

var c = { f:'see' };
var a = { fa: c };
var b = { fb: c };
var cloneA = $.extend({}, a);
var cloneB = $.extend({}, b);
console.log(a.fa === b.fb, cloneA.fa === cloneB.fb, a.fa === cloneB.fb);
// true true true

但是您似乎想克隆所有对象(包括c),同时跟踪对象的关系。为此,最好使用对象关系表。

我经常在嵌套的 javascript 对象和 JSON 中看到这一点,因为人们往往会忘记 JSON 纯粹是一种文本格式。JSON 文件中没有实际的 javascript 对象,除了单个字符串 text instanceof String。javascript 中没有豆类、泡菜或任何含有大量防腐剂的食物。

在对象关系表中,每个“表”只是一个“平面”对象数组,只有原始值属性和指向表(或另一个表)中其他对象的指针(不是引用)。指针可以只是目标对象的索引。

所以上述对象关系的 JSON 版本可能看起来像

{
    "table-1":[
        { "a": { "fa":["table-2",0] } },
        { "b": { "fb":["table-2",0] } }
    ],
    "table-2":[
        { "c": { "name":"see" } },
        { "d": { "name":"dee" } },
        { "e": { "name":"eh.."} }
    ]
}

解析器可能看起来像

var tables = JSON.parse(jsonString);
for(var key in tables){
    var table = tables[key];
    for(var i = 0; i < table.length; i++){
        var name = Object.keys(table[i])
        var obj = table[i][name];
        for(var key2 in obj){
            if(obj[key2] instanceof Array && obj[key2][0] in tables){
                var refTable = obj[key2][0];
                var refID    = obj[key2][1];
                var refObj   = tables[refTable][refID];
                var refKey   = Object.keys(refObj)[0];
                obj[key2] = refObj[refKey];
            }
        }
        this[name] = obj;
    }
}

console.log(a.fa === b.fb, b.fb === c);
// true true

我意识到对象关系映射有它的缺点,但是拍摄脚本引擎的快照确实听起来有点疯狂。特别是因为您的意图是能够回忆起之前的每一步,因为这样您就需要为每一步创建一个新快照……这将很快占用大量磁盘空间……除非您只是跟踪快照每个步骤之间的差异,例如 git repo。听起来要实现一个看似简单的“撤消”方法需要做大量的工作。

该死的..想一想,为什么不把每一步都存储在历史文件中呢?然后,如果您需要后退一步,只需在上一步截断历史文件,然后在新环境中再次运行每个步骤。

不确定使用 java 会有多实用(性能方面)。Nodejs(就像今天一样)比任何 Java 脚本引擎都快。事实上,从现在开始我就叫它 ECMAscript。对不起,咆哮,就是这样

Java 很慢,但你已经知道了。
因为它很容易显示,所以速度很快。

于 2014-07-10T09:52:56.933 回答
2

我可以建议一种不同的方法。

  • 不要试图克隆 ScriptEngine。它不实现 Serializable/Externalizable 并且 API 不支持克隆。强制克隆的尝试将是可能会在未来的 Java 版本中中断的变通方法。

  • 使用cycle.js将绑定序列化为 JSON 。它将以形式对对象引用进行编码{$ref: PATH}。当您反序列化时,它将恢复引用。

    据我所知,cycle.js不会序列化函数,但可以使用 Function.toString() 自己添加函数序列化(见下文和示例

或者,如果使用库不是一种选择,那么实现您自己的序列化以满足您的需求相当简单:

var jsonString = JSON.stringify(obj, function(key, val) {
    if (typeof(value) === 'function')
        return val.toString();
    // or do something else with value like detect a reference
    return val
})
于 2014-07-11T09:49:23.003 回答