下面是一个带有循环引用的数据结构示例:
function makeToolshed(){
var nut = {name: 'nut'}, bolt = {name: 'bolt'};
nut.needs = bolt; bolt.needs = nut;
return { nut: nut, bolt: bolt };
}
当您希望 KEEP 循环引用(在反序列化时恢复它们,而不是“破坏”它们),您有 2 个选择,我将比较这里。首先是 Douglas Crockford 的 cycle.js,其次是我的 siberia 包。两者都首先“回收”对象,即构造另一个“包含相同信息”的对象(没有任何循环引用)。
先生。康乐福先行:
JSON.decycle(makeToolshed())
如您所见,JSON 的嵌套结构被保留了,但有一个新的东西,即具有特殊$ref 属性的对象。让我们看看它是如何工作的。
root = makeToolshed();
[root.bolt === root.nut.needs, root.nut.needs.needs === root.nut]; // retutrns [true,true]
美元符号代表根。 .bolt 拥有$ref 告诉我们.bolt 是一个“已经见过”的对象,并且那个特殊属性的值(这里是字符串 $["nut"]["needs"])告诉我们在哪里,见第一个=== 上面。同样适用于第二个$ref 和第二个===。
让我们使用合适的深度相等测试(即从接受的答案到this question 的 Anders Kaseorg 的 deepGraphEqual 函数)来看看克隆是否有效。
root = makeToolshed();
clone = JSON.retrocycle(JSON.decycle(root));
deepGraphEqual(root, clone) // true
serialized = JSON.stringify(JSON.decycle(root));
clone2 = JSON.retrocycle(JSON.parse(serialized));
deepGraphEqual(root, clone2); // true
现在,西伯利亚:
JSON.Siberia.forestify(makeToolshed())
西伯利亚不会尝试模仿“经典”JSON,没有嵌套结构。对象图以“平面”方式描述。
对象图的每个节点都变成了一个扁平树(纯键值对列表,只有整数值),它是.forest. 中的一个条目,在索引零处,我们找到根对象,在更高的索引处,我们找到对象图的其他节点和(森林中某棵树的某个键的某个键的)负值指向atoms 数组(通过 types 数组输入,但我们将在此处跳过输入细节)。所有终端节点都在原子表中,所有非终端节点都在森林表中,您可以立即看到对象图有多少个节点,即forest.length。让我们测试它是否有效:
root = makeToolshed();
clone = JSON.Siberia.unforestify(JSON.Siberia.forestify(root));
deepGraphEqual(root, clone); // true
serialized = JSON.Siberia.stringify(JSON.Siberia.forestify(root));
clone2 = JSON.Siberia.unforestify(JSON.Siberia.unstringify(serialized));
deepGraphEqual(root, clone2); // true
比较
稍后会添加部分。
注意
我目前正在重构包。中心思想和算法保持不变,但新版本将更易于使用,顶级 API 将有所不同。我将很快归档 siberia 并展示重构的版本,我将其称为 objectgraph。敬请期待,它将在本月(2020 年 8 月)发生
啊,和超短版比较。对于“指针”,我需要与整数一样多的空间,因为我的“指向已经看到的节点的指针”(事实上,指向所有节点,无论是否已经看到)是整数。在 Crockford 先生的版本中,存储“指针”所需的数量仅受对象图大小的限制。这使得 Crockford 先生的版本的最坏情况复杂性极其可怕。 Crockford 先生给了我们“另一个 Bubblesort”。我不是在开玩笑。真是太糟糕了。如果您不相信,这里有测试,您可以从包的自述文件开始找到它们(也将在本月,2020 年 8 月将它们转换为符合 benchmark.js)