In Javascript, when performing a deep copy, how do I avoid a cycle, due to a property being “this”?
我有一些图书馆代码在我身上不断循环。
我不清楚如何在JavaScript中最好地执行周期检测和避免。也就是说,没有程序化的方法来检查对象是否来自"this"引用,是吗?
这是密码。谢谢!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | setAttrs: function(config) { var go = Kinetic.GlobalObject; var that = this; // set properties from config if(config !== undefined) { function setAttrs(obj, c) { for(var key in c) { var val = c[key]; /* * if property is an object, then add an empty object * to the node and then traverse */ if(go._isObject(val) && !go._isArray(val) && !go._isElement(val)) { if(obj[key] === undefined) { obj[key] = {}; } setAttrs(obj[key], val); // <--- offending code; // one of my"val"s is a"this" reference // to an enclosing object } |
我所知道的处理这种情况的"可靠且干净"的方法是使用一组"访问过的"对象,然后根据当前对象是否已经"访问过"来反应——终止、插入符号引用等。
Crockford先生在cycle.js中使用了这种方法,并使用了一个数组来收集数据。Excerpt:
1 2 3 4 5 6 7 8 9 | // If the value is an object or array, look to see if we have already // encountered it. If so, return a $ref/path object. This is a hard way, // linear search that will get slower as the number of unique objects grows. for (i = 0; i < objects.length; i += 1) { if (objects[i] === value) { return {$ref: paths[i]}; } } |
不幸的是,在JavaScript中不可能使用原始的"hash"方法,因为它缺少标识映射。虽然数组集合边界是
这是因为,如果"已访问"集合只是一个保护,那么EDOCX1[1]的值只是堆栈的深度:当多次复制同一对象时,只有循环才是重要的。也就是说,可以在堆栈展开时修剪"已访问"集合中的对象。
在cycle.js代码中,不能修剪"visited"集合,因为它必须确保始终使用给定对象的相同符号名,这样可以在还原时使序列化"维护引用"。然而,即使在这种情况下,
我能想到的唯一其他方法将需要直接向被遍历的对象添加一个"访问的属性",我认为这是一个通常不需要的特性。(不过,请参见Bergi关于这个工件相对容易清理的评论。)
快乐编码。
好吧,我对上面提到的"访问过"的财产@pst的外观很感兴趣,所以我对其进行了编码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | Object.copyCircular = function deepCircularCopy(o) { const gdcc ="__getDeepCircularCopy__"; if (o !== Object(o)) return o; // primitive value var set = gdcc in o, cache = o[gdcc], result; if (set && typeof cache =="function") return cache(); // else o[gdcc] = function() { return result; }; // overwrite if (o instanceof Array) { result = []; for (var i=0; i<o.length; i++) { result[i] = deepCircularCopy(o[i]); } else { result = {}; for (var prop in o) if (prop != gdcc) result[prop] = deepCircularCopy(o[prop]); else if (set) result[prop] = deepCircularCopy(cache); } if (set) o[gdcc] = cache; // reset else delete o[gdcc]; // unset again return result; }; |
注意,这只是一个例子。它不支持:
- 非平面对象。所有带有原型的东西(数组除外)将不会被克隆,而是复制到
new Object !这包括功能! - 跨全局范围对象:使用
instanceof Array 。 - 属性描述符,如setters/getter、unwritable和unnumerable属性
好吃的东西:
- 它不使用每次遇到对象时都需要搜索的大数组。
缺点:
- 不适用于具有不符合其要求的
__getDeepCircularCopy__ 方法的对象。尽管在这个轻量级版本中不支持方法(以函数作为值的属性)。
此解决方案将处理具有循环引用的对象,复制循环结构,而不以无限循环结束。请注意,"循环"在这里意味着属性引用了"树"中的一个"父级":
1 2 3 4 5 6 7 8 9 | [Object]_ [Object]_ / |\ / |\ prop | prop | \_____/ | | \|/ | [Object] | \ | prop | \___/ |
共享一片叶子的树的结构不会被复制,它们将成为两个独立的叶子:
1 2 3 4 5 6 7 8 9 | [Object] [Object] / \ / \ / \ / \ |/_ _\| |/_ _\| [Object] [Object] ===> [Object] [Object] \ / | | \ / | | _\| |/_ \|/ \|/ [Object] [Object] [Object] |
除非您想跟踪复制的每个属性。
但是,如果您确定每个属性都是
1 2 3 4 5 6 7 | try { JSON.stringify(obj); // It's ok to make a deep copy of obj } catch (e) { // obj has back references and a deep copy would generate an infinite loop // Or finite, i.e. until the stack space is full. } |
这只是一个想法,我对表演一无所知。我担心在大型物体上速度会很慢。
这是一个简单的递归克隆方法。与许多其他解决方案一样,大多数非基本属性将与原始对象(如函数)共享引用。
它通过保留被引用对象的映射来处理无限循环,以便后续引用可以共享同一克隆。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | const georgeCloney = (originalObject, __references__ = new Map()) => { if(typeof originalObject !=="object" || originalObject === null) { return originalObject; } // If an object has already been cloned then return a // reference to that clone to avoid an infinite loop if(__references__.has(originalObject) === true) { return __references__.get(originalObject); } let clonedObject = originalObject instanceof Array ? [] : {}; __references__.set(originalObject, clonedObject); for(let key in originalObject) { if(originalObject.hasOwnProperty(key) === false) { continue; } clonedObject[key] = georgeCloney(originalObject[key], __references__); } return clonedObject; }; |
示例用法…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | let foo = {}; foo.foo = foo; let bar = georgeCloney(foo); bar.bar ="Jello World!"; // foo output // { // foo: { // foo: {...} // } // } // // bar output // { // foo: { // foo: {...}, // bar:"Jello World!" // }, // bar:"Jello World!" // } |